<!DOCTYPE html>


<html lang="zh_CN">
  

    <head>
      <meta charset="utf-8" />
        
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title>RabbitMQ |  MyBlog</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/favicon.ico" />
       
<link rel="stylesheet" href="/dist/main.css">

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css"
      />
      
<link rel="stylesheet" href="/css/custom.css">
 
      <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    <link rel="alternate" href="/atom.xml" title="MyBlog" type="application/atom+xml">
</head>
  </html>
</html>


<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="消息中间件-RabbitMQ笔记"
  class="article article-type-消息中间件"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  RabbitMQ
</h1>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2022/02/21/RabbitMQ%E7%AC%94%E8%AE%B0/" class="article-date">
  <time datetime="2022-02-21T06:16:34.000Z" itemprop="datePublished">2022-02-21</time>
</a>   
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">23.7k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">98 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="搭建虚拟机注意事项"><a href="#搭建虚拟机注意事项" class="headerlink" title="搭建虚拟机注意事项"></a>搭建虚拟机注意事项</h1><p>CentOS8如果出现以下情况：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201312025935.png" alt="image-20220131202543250"></p>
<p><strong>解决方法：</strong></p>
<p>1.备份现有源</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mv /etc/yum.repos.d /etc/yum.repos.d.backup</span><br></pre></td></tr></table></figure>

<p>2.设置新的yum目录</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir /etc/yum.repos.d</span><br></pre></td></tr></table></figure>

<p>3.安装wget</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install -y wget</span><br></pre></td></tr></table></figure>

<p>4.就是坑了我一下午的下载配置(大家一定要区分自己的系统版本，不然肯定不通过)<br>CentOS 5</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo</span><br></pre></td></tr></table></figure>

<p>CentOS 6</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo</span><br></pre></td></tr></table></figure>

<p>CentOS 7</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo</span><br></pre></td></tr></table></figure>

<p>CentOS 8</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo</span><br></pre></td></tr></table></figure>

<p>5.清除文件并重建元数据缓存</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">yum clean all</span><br><span class="line">yum makecache</span><br></pre></td></tr></table></figure>

<p>6.最后更新软件包，这一步要下载安装或升级一百多个软件包，比较费时间，大概三五分钟吧！</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum update -y</span><br></pre></td></tr></table></figure>



<h1 id="一、MQ的相关概念"><a href="#一、MQ的相关概念" class="headerlink" title="一、MQ的相关概念"></a>一、MQ的相关概念</h1><h2 id="1-1-1-什么是MQ"><a href="#1-1-1-什么是MQ" class="headerlink" title="1.1.1. 什么是MQ"></a>1.1.1. 什么是MQ</h2><p>MQ(message queue)，从字面意思上看，本质是个队列，FIFO先入先出，只不过队列中存放的内容是message而已，还是一种跨进程的通信机制，用于上下游传递消息。在互联网架构中，MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用了MQ之后，消息发送上游只需要依赖MQ，不用依赖其他服务。 </p>
<h2 id="1-1-2-为什么要用MQ"><a href="#1-1-2-为什么要用MQ" class="headerlink" title="1.1.2.为什么要用MQ"></a>1.1.2.为什么要用MQ</h2><h3 id="1-流量消峰"><a href="#1-流量消峰" class="headerlink" title="1.流量消峰"></a>1.流量消峰</h3><p>使访问的人进行排队，防止订单系统宕机</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202112161103427.png" alt="image-20211216110323639"></p>
<h3 id="2-应用解耦"><a href="#2-应用解耦" class="headerlink" title="2.应用解耦"></a>2.应用解耦</h3><p>以电商应用为例，应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后，如果耦合调用库存系统、物流系统、支付系统，任何一个子系统出了故障，都会造成下单操作异常。当转变成基于消息队列的方式后，系统间调用的问题会减少很多，比如物流系统因为发生故障，需要几分钟来修复。在这几分钟的时间里，物流系统要处理的内存被缓存在消息队列中，用户的下单操作可以正常完成。当物流系统恢复后，继续处理订单信息即可，中单用户感受不到物流系统的故障，提示系统的可用性。</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202112161316052.png" alt="image-20211216131630841"></p>
<h3 id="3-异步处理"><a href="#3-异步处理" class="headerlink" title="3.异步处理"></a>3.异步处理</h3><p>A调用B服务后，只需要监听B处理完成的消息，当B处理完成后，会发送一条消息给MQ，MQ会将此消息转发给A服务。这样A服务既不用循环调用B的查询API，也不用提供 callback api。同样 B 服务也不用做这些操作。A 服务还能及时的得到异步处理成功的消息</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202112161900728.png" alt="image-20211216190058422"></p>
<h2 id="1-1-3-MQ的分类"><a href="#1-1-3-MQ的分类" class="headerlink" title="1.1.3.MQ的分类"></a>1.1.3.MQ的分类</h2><h3 id="1-ActiveMQ"><a href="#1-ActiveMQ" class="headerlink" title="1.ActiveMQ"></a>1.ActiveMQ</h3><p>优点：单机吞吐量万级，时效性ms级，可用性高，基于主从架构实现高可用性。</p>
<p>缺点:官方社区现在对ActiveMQ 5.x维护越来越少，高吞吐量场景较少使用。 </p>
<h3 id="2-Kafka"><a href="#2-Kafka" class="headerlink" title="2.Kafka"></a>2.Kafka</h3><p>大数据的杀手锏，谈到大数据领域内的消息传输，则绕不开Kafka，这款为大数据而生的消息中间件，以其百万级TPS的吞吐量名声大噪，迅速成为大数据领域的宠儿，在数据采集、传输、存储的过程中发挥着举足轻重的作用。目前已经被LinkedIn，Uber, Twitter, Netflix等大公司所采纳。  </p>
<p>优点: 性能卓越，单机写入TPS约在百万条/秒，最大的优点，就是吞吐量高。时效性ms级可用性非常高，kafka是分布式的，一个数据多个副本，少数机器宕机，不会丢失数据，不会导致不可用,消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次;有优秀的第三方Kafka Web管理界面Kafka-Manager；在日志领域比较成熟，被多家公司和多个开源项目使用；功能支持：功能较为简单，主要支持简单的MQ功能，在大数据领域的实时计算以及日志采集被大规模使用  </p>
<p>缺点：Kafka单机超过64个队列/分区，Load会发生明显的飙高现象，队列越多，load越高，发送消息响应时间变长，使用短轮询方式，实时性取决于轮询间隔时间，消费失败不支持重试；支持消息顺序，但是一台代理宕机后，就会产生消息乱序，社区更新较慢； </p>
<h3 id="3-RocketMQ"><a href="#3-RocketMQ" class="headerlink" title="3.RocketMQ"></a>3.RocketMQ</h3><p>RocketMQ出自阿里巴巴的开源产品，用Java语言实现，在设计时参考了 Kafka，并做出了自己的一些改进。被阿里巴巴广泛应用在订单，交易，充值，流计算，消息推送，日志流式处理，binglog分发等场景。</p>
<p> 优点:单机吞吐量十万级,可用性非常高，分布式架构,消息可以做到0丢失,MQ功能较为完善，还是分布式的，扩展性好,支持10亿级别的消息堆积，不会因为堆积导致性能下降,源码是java我们可以自己阅读源码，定制自己公司的MQ  </p>
<p>缺点：支持的客户端语言不多，目前是java及c++，其中c++不成熟；社区活跃度一般,没有在MQ核心中去实现JMS等接口,有些系统要迁移需要修改大量代码 </p>
<h3 id="4-RabbitMQ"><a href="#4-RabbitMQ" class="headerlink" title="4.RabbitMQ"></a>4.RabbitMQ</h3><p>2007年发布，是一个在AMQP(高级消息队列协议)基础上完成的，可复用的企业消息系统，是当前最主流的消息中间件之一。 </p>
<p> 优点:由于erlang语言的高并发特性，性能较好；吞吐量到万级，MQ功能比较完备,健壮、稳定、易用、跨平台、支持多种语言 如：Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等，支持AJAX文档齐全；开源提供的管理界面非常棒，用起来很好用,社区活跃度高；更新频率相当高 <a target="_blank" rel="noopener" href="https://www.rabbitmq.com/news.html">https://www.rabbitmq.com/news.html</a>  </p>
<p>缺点：商业版需要收费,学习成本较高 </p>
<h2 id="1-1-4MQ-的选择"><a href="#1-1-4MQ-的选择" class="headerlink" title="1.1.4MQ 的选择"></a>1.1.4MQ 的选择</h2><h3 id="1-Kafka"><a href="#1-Kafka" class="headerlink" title="1.Kafka"></a>1.Kafka</h3><p>Kafka 主要特点是基于 Pull 的模式来处理消息消费，追求高吞吐量，一开始的目的就是用于日志收集和传输，适合产生<strong>大量数据</strong>的互联网服务的数据收集业务。<strong>大型公司</strong>建议可以选用，如果有<strong>日志采集</strong>功能，肯定是首选 kafka 了。尚硅谷官网 kafka 视频连接 <a target="_blank" rel="noopener" href="http://www.gulixueyuan.com/course/330/tasks">http://www.gulixueyuan.com/course/330/tasks</a></p>
<h3 id="2-RocketMQ"><a href="#2-RocketMQ" class="headerlink" title="2.RocketMQ"></a>2.RocketMQ</h3><p>天生为<strong>金融互联网</strong>领域而生，对于可靠性要求很高的场景，尤其是电商里面的订单扣款，以及业务削峰，在大量交易涌入时，后端可能无法及时处理的情况。RoketMQ 在稳定性上可能更值得信赖，这些业务场景在阿里双 11 已经经历了多次考验，如果你的业务有上述并发场景，建议可以选择 RocketMQ。</p>
<h3 id="3-RabbitMQ"><a href="#3-RabbitMQ" class="headerlink" title="3.RabbitMQ"></a>3.RabbitMQ</h3><p>结合 erlang 语言本身的并发优势，性能好<strong>时效性微秒级</strong>，<strong>社区活跃度也比较高</strong>，管理界面用起来十分方便，如果你的<strong>数据量没有那么大</strong>，中小型公司优先选择功能比较完备的 RabbitMQ。</p>
<h1 id="二、RabbitMQ"><a href="#二、RabbitMQ" class="headerlink" title="二、RabbitMQ"></a>二、RabbitMQ</h1><h2 id="2-1-RabbitMQ-的概念"><a href="#2-1-RabbitMQ-的概念" class="headerlink" title="2.1.RabbitMQ 的概念"></a>2.1.RabbitMQ 的概念</h2><p>RabbitMQ 是一个消息中间件：它接受并转发消息。你可以把它当做一个快递站点，当你要发送一个包裹时，你把你的包裹放到快递站，快递员最终会把你的快递送到收件人那里，按照这种逻辑 RabbitMQ 是一个快递站，一个快递员帮你传递快件。RabbitMQ 与快递站的主要区别在于，它不处理快件而是接收，存储和转发消息数据。</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201172243114.png" alt="image-20220117224309901"></p>
<h2 id="2-2-四大核心概念"><a href="#2-2-四大核心概念" class="headerlink" title="2.2.四大核心概念"></a>2.2.四大核心概念</h2><p><strong>生产者</strong></p>
<p>产生数据发送消息的程序是生产者</p>
<p><strong>交换机</strong></p>
<p>交换机是 RabbitMQ 非常重要的一个部件，一方面它接收来自生产者的消息，另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息，是将这些消息推送到特定队列还是推送到多个队列，亦或者是把消息丢弃，这个得有交换机类型决定</p>
<p><strong>队列</strong></p>
<p>队列是 RabbitMQ 内部使用的一种数据结构，尽管消息流经 RabbitMQ 和应用程序，但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束，本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列，许多消费者可以尝试从一个队列接收数据。这就是我们使用队列的方式</p>
<p><strong>消费者</strong></p>
<p>消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。请注意生产者，消费者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201172246607.png" alt="image-20220117224632395"></p>
<h2 id="2-3-RabbitMQ核心"><a href="#2-3-RabbitMQ核心" class="headerlink" title="2.3.RabbitMQ核心"></a>2.3.RabbitMQ核心</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201191120331.png" alt="image-20220119112018515"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201191121320.png" alt="image-20220119112111857"></p>
<h2 id="2-4-安装"><a href="#2-4-安装" class="headerlink" title="2.4.安装"></a>2.4.安装</h2><p>1.官网地址</p>
<p><a target="_blank" rel="noopener" href="https://www.rabbitmq.com/download.html">https://www.rabbitmq.com/download.html</a></p>
<p>2.文件上传</p>
<p>上传到/usr/local/software 目录下(如果没有 software 需要自己创建) </p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201172304096.png" alt="image-20220117230454052"></p>
<p>3.安装文件(分别按照以下顺序安装)</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rpm -ivh erlang-23.3.4.10-1.el8.x86_64.rpm</span><br><span class="line">yum install socat -y</span><br><span class="line">rpm -ivh rabbitmq-server-3.9.12-1.el8.noarch.rpm</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201180007498.png" alt="image-20220118000742120"></p>
<p>若在使用yum命令过程中出现以上情况，则使用</p>
<p>vim /etc/yum.repos.d/epel.repo 命令，将以下内容注释</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201180006846.png" alt="image-20220118000619936"></p>
<p>3.常用命令(按照以下顺序执行)</p>
<p>添加开机启动 RabbitMQ 服务</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chkconfig rabbitmq-server on</span><br></pre></td></tr></table></figure>

<p>启动服务</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/sbin/service rabbitmq-server start </span><br></pre></td></tr></table></figure>

<p>查看服务状态</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/sbin/service rabbitmq-server status</span><br></pre></td></tr></table></figure>

<p>停止服务(选择执行)</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/sbin/service rabbitmq-server stop </span><br></pre></td></tr></table></figure>

<p>开启 web 管理插件</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rabbitmq-plugins <span class="built_in">enable</span> rabbitmq_management</span><br></pre></td></tr></table></figure>

<p><font color='red'>若出现  {:query, :rabbit@yuan, {:badrpc, :timeout}} 这个问题</font></p>
<p>则使用vim /etc/hosts添加以下内容</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201181421331.png" alt="image-20220118142129799"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201181421846.png" alt="image-20220118142111511"></p>
<p>用默认账号密码为(guest)访问地址 <a target="_blank" rel="noopener" href="http://192.168.227.50:15672/%E5%87%BA%E7%8E%B0%E6%9D%83%E9%99%90%E9%97%AE%E9%A2%98">http://192.168.227.50:15672/出现权限问题</a><font color='red'><strong>我们需要注意的是需要先关闭防火墙才可访问</strong></font></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201181423214.png" alt="image-20220118142308143"></p>
<h2 id="2-5-添加一个新的用户"><a href="#2-5-添加一个新的用户" class="headerlink" title="2.5.添加一个新的用户"></a>2.5.添加一个新的用户</h2><p>创建账号</p>
<p>rabbitmqctl add_user admin 123</p>
<p>设置用户角色</p>
<p>rabbitmqctl set_user_tags admin administrator</p>
<p>设置用户权限</p>
<p>set_permissions [-p <vhostpath>] <user> <conf> <write> <read></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl set_permissions -p <span class="string">&quot;/&quot;</span> admin  <span class="string">&quot;.*“ &quot;</span>.*<span class="string">&quot; &quot;</span>.*<span class="string">&quot;</span></span><br></pre></td></tr></table></figure>

<p>用户 user_admin 具有/vhost1 这个 virtual host 中所有资源的配置、写、读权限</p>
<p>当前用户和角色</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl list_users</span><br></pre></td></tr></table></figure>

<h1 id="三、Hello-World"><a href="#三、Hello-World" class="headerlink" title="三、Hello World"></a>三、Hello World</h1><h2 id="1-依赖"><a href="#1-依赖" class="headerlink" title="1.依赖"></a>1.依赖</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--指定 jdk 编译版本--&gt;</span> </span><br><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">source</span>&gt;</span>8<span class="tag">&lt;/<span class="name">source</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">target</span>&gt;</span>8<span class="tag">&lt;/<span class="name">target</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span> </span><br><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--rabbitmq 依赖客户端--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.rabbitmq<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>amqp-client<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>5.8.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--操作文件流的一个依赖--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>commons-io<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>commons-io<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.6<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h2 id="2-生产者"><a href="#2-生产者" class="headerlink" title="2.生产者"></a>2.生产者</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Producer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">//创建连接工厂</span></span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        <span class="comment">//工厂ip连接RabbitMQ队列</span></span><br><span class="line">        factory.setHost(HOST);</span><br><span class="line">        <span class="comment">//用户名</span></span><br><span class="line">        factory.setUsername(<span class="string">&quot;admin&quot;</span>);</span><br><span class="line">        <span class="comment">//密码</span></span><br><span class="line">        factory.setPassword(<span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//创建连接</span></span><br><span class="line">        Connection connection = factory.newConnection();</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 生成队列</span></span><br><span class="line"><span class="comment">         * 1.队列名称</span></span><br><span class="line"><span class="comment">         * 2.队列中的消息是否持久化（磁盘），默认情况消息存储在内存中</span></span><br><span class="line"><span class="comment">         * 3.是否只供一个消费者进行消费，是否进行消息的共享，true可以多个消费者进行消费，默认情况下是false</span></span><br><span class="line"><span class="comment">         * 4.是否自动删除，最后一个消费者开连接以后，该队列是否自动删除，true自动删除，反之亦然。</span></span><br><span class="line"><span class="comment">         * 5.其它参数</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        channel.queueDeclare(QUEUE_NAME,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">        <span class="comment">//发消息</span></span><br><span class="line">        String message = <span class="string">&quot;hello world&quot;</span>;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 参数含义：</span></span><br><span class="line"><span class="comment">         * 1.发送到哪个交换机</span></span><br><span class="line"><span class="comment">         * 2.路由的key值是哪个，本次是队列的名称</span></span><br><span class="line"><span class="comment">         * 3.其他参数信息</span></span><br><span class="line"><span class="comment">         * 4.发送消息的消息体</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        channel.basicPublish(<span class="string">&quot;&quot;</span>,QUEUE_NAME,<span class="keyword">null</span>,message.getBytes());</span><br><span class="line">        System.out.println(<span class="string">&quot;====消息发送完毕====&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="3-消费者"><a href="#3-消费者" class="headerlink" title="3.消费者"></a>3.消费者</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Consumer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, TimeoutException </span>&#123;</span><br><span class="line">        <span class="comment">//创建连接工厂</span></span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        factory.setHost(HOST);</span><br><span class="line">        factory.setUsername(<span class="string">&quot;admin&quot;</span>);</span><br><span class="line">        factory.setPassword(<span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//创建连接</span></span><br><span class="line">        Connection connection = factory.newConnection();</span><br><span class="line">        <span class="comment">//创建信道</span></span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        <span class="comment">//接收消息</span></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 参数说明：</span></span><br><span class="line"><span class="comment">         * 1.消费哪个队列</span></span><br><span class="line"><span class="comment">         * 2.消费成功后是否自动签收</span></span><br><span class="line"><span class="comment">         * 3.消费者成功消费的回调</span></span><br><span class="line"><span class="comment">         * 4.消费者取消消费的回调</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>,</span><br><span class="line">                (s,delivery) -&gt; System.out.println(<span class="keyword">new</span> String(delivery.getBody())), <span class="comment">//打印消息体</span></span><br><span class="line">                (s) -&gt; System.out.println(<span class="string">&quot;消息被中断&#123;&#125;&quot;</span>+s)</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="四、Work-Queue"><a href="#四、Work-Queue" class="headerlink" title="四、Work Queue"></a>四、Work Queue</h1><p>工作队列（又称任务队列）的主要思想是避免立即执行资源密集型任务，而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时，这些工作线程将一起处理这些任务。 </p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201191647935.png" alt="image-20220119164740858"></p>
<p>工作线程之间是竞争关系</p>
<h2 id="1-轮询分发消息"><a href="#1-轮询分发消息" class="headerlink" title="1.轮询分发消息"></a>1.轮询分发消息</h2><h3 id="1-1-抽取连接工厂工具类"><a href="#1-1-抽取连接工厂工具类" class="headerlink" title="1.1.抽取连接工厂工具类"></a>1.1.抽取连接工厂工具类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RabbitMQUtils</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Channel <span class="title">getChannel</span><span class="params">(String host, String username, String password)</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">        <span class="comment">//创建一个连接工厂</span></span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        factory.setHost(host);</span><br><span class="line">        factory.setUsername(username);</span><br><span class="line">        factory.setPassword(password);</span><br><span class="line">        <span class="comment">//创建连接</span></span><br><span class="line">        Connection connection = factory.newConnection();</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        <span class="keyword">return</span> channel;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="1-2启动两个工作线程"><a href="#1-2启动两个工作线程" class="headerlink" title="1.2启动两个工作线程"></a>1.2启动两个工作线程</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WorkConsumer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;消费者2...&quot;</span>);</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        channel.basicConsume(QUEUE_NAME,<span class="keyword">true</span>,</span><br><span class="line">                (a,b) -&gt; System.out.println(<span class="string">&quot;接收到消息:&quot;</span>+<span class="keyword">new</span> String(b.getBody())),</span><br><span class="line">                (message) -&gt; System.out.println(<span class="string">&quot;消息被中断&#123;&#125;&quot;</span>+message));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="1-3启动一个发送线程"><a href="#1-3启动一个发送线程" class="headerlink" title="1.3启动一个发送线程"></a>1.3启动一个发送线程</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WorkProducer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//队列声明</span></span><br><span class="line">        channel.queueDeclare(QUEUE_NAME,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">        Scanner sc = <span class="keyword">new</span> Scanner(System.in);</span><br><span class="line">        <span class="keyword">while</span>(sc.hasNext())&#123;</span><br><span class="line">            String message = sc.next();</span><br><span class="line">            channel.basicPublish(<span class="string">&quot;&quot;</span>,QUEUE_NAME,<span class="keyword">null</span>,message.getBytes());</span><br><span class="line">            System.out.println(<span class="string">&quot;发送完消息:&quot;</span>+message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="2-消息应答"><a href="#2-消息应答" class="headerlink" title="2.消息应答"></a>2.消息应答</h2><h3 id="2-1概念"><a href="#2-1概念" class="headerlink" title="2.1概念"></a>2.1概念</h3><p>消费者完成一个任务可能需要一段时间，如其中一个消费者处理一个长的任务并且只完成部分突然消费者挂掉，会发送什么情况。RabbitMQ一旦向消费者传递一条消息，便立即将该消息标为删除。在这种情况下，突然有个消费者挂掉，我们将丢失正在处理的消息，以及后续发送给消费者的消息，因为它无法接收。</p>
<p><strong>消息应答就是：消费者在接收到消息并且处理该消息之后，告诉rabbitMQ它已经处理了，rabbitmq可以把该消息删除了</strong></p>
<h3 id="2-2自动应答"><a href="#2-2自动应答" class="headerlink" title="2.2自动应答"></a>2.2自动应答</h3><p>消息发送后立即被认为已经传送成功，这种模式需要在<strong>高吞吐量和数据传输安全性方面做权衡</strong>,因为这种模式如果消息在接收到之前，消费者那边出现连接或者 channel 关闭，那么消息就丢失了,当然另一方面这种模式消费者那边可以传递过载的消息，<strong>没有对传递的消息数量进行限制</strong>，当然这样有可能使得消费者这边由于接收太多还来不及处理的消息，导致这些消息的积压，最终使得内存耗尽，最终这些消费者线程被操作系统杀死，<strong>所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用</strong>。</p>
<h3 id="2-3消息应答的方法"><a href="#2-3消息应答的方法" class="headerlink" title="2.3消息应答的方法"></a>2.3消息应答的方法</h3><ol>
<li>Channel.basicAck(用于肯定确认)<ul>
<li>RabbitMQ已知道该消息并且成功的处理消息，可以将其丢弃</li>
</ul>
</li>
<li>Channel.basicNack(用于否定确认)</li>
<li>Channel.basicReject(用于否定确认)<ul>
<li>与Channel.basicNack相比少一个参数</li>
<li>不处理该消息了直接拒绝，可以将其丢弃</li>
</ul>
</li>
</ol>
<h3 id="2-4Multiple的解释"><a href="#2-4Multiple的解释" class="headerlink" title="2.4Multiple的解释"></a>2.4Multiple的解释</h3><p>手动应答的好处是可以<strong>批量</strong>应答并且减少网络拥堵</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.basicAck(deliveryTag,<span class="keyword">true</span>);</span><br></pre></td></tr></table></figure>

<p>multiple的true和false的含义：</p>
<ul>
<li>true代表批量应答channel上未应答的消息，比如说channel上有传送tag的消息5，6，7，8。当前tag是8，那么此时5-8的这些为应答的消息都会被确认收到消息应答</li>
<li>false同上面相比，只会应答tag=8的消息，5，6，7这三个消息依然不会被确认收到消息应答</li>
</ul>
<p><font color='red'>建议：尽量不要使用批量应答</font></p>
<h3 id="2-5消息自动重新入队"><a href="#2-5消息自动重新入队" class="headerlink" title="2.5消息自动重新入队"></a>2.5消息自动重新入队</h3><p>如果消费者由于某些原因失去连接（其通道已关闭，连接已关闭或TCP连接丢失），导致消息未发送ACK确认，RabbitMQ将了解到消息未完全处理，并将对其重新排队。如果此时其他消费者可以处理，它将很快将其重新分发给其他消费者。这样，即使某个消费者突然死亡，也可以确保数据不会丢失。</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201201446347.png" alt="image-20220120144648809"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201201450689.png" alt="image-20220120145041069"></p>
<h3 id="2-6消息手动应答代码"><a href="#2-6消息手动应答代码" class="headerlink" title="2.6消息手动应答代码"></a>2.6消息手动应答代码</h3><p>生产者代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Task</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TASK_QUEUE_NAME = <span class="string">&quot;ack_queue&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//声明队列</span></span><br><span class="line">        channel.queueDeclare(TASK_QUEUE_NAME,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">        <span class="comment">//发送消息</span></span><br><span class="line">        Scanner sc = <span class="keyword">new</span> Scanner(System.in);</span><br><span class="line">        <span class="keyword">while</span>(sc.hasNext())&#123;</span><br><span class="line">            String message = sc.next();</span><br><span class="line">            channel.basicPublish(<span class="string">&quot;&quot;</span>,TASK_QUEUE_NAME,<span class="keyword">null</span>,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">            System.out.println(<span class="string">&quot;生产者发出消息:&quot;</span>+message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>消费者1代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Consumer01</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TASK_QUEUE_NAME = <span class="string">&quot;ack_queue&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;C1等待接收处理消息时间短&quot;</span>);</span><br><span class="line">        channel.basicConsume(TASK_QUEUE_NAME,<span class="keyword">false</span>,</span><br><span class="line">                (consumerTag,delivery) -&gt; &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        TimeUnit.SECONDS.sleep(<span class="number">1</span>);</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                        e.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line">                    System.out.println(<span class="string">&quot;接收到消息:&quot;</span>+<span class="keyword">new</span> String(delivery.getBody(), StandardCharsets.UTF_8));</span><br><span class="line">                    <span class="comment">//1.消息的标记tag 2.false，不进行批量处理</span></span><br><span class="line">                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(),<span class="keyword">false</span>);</span><br><span class="line">                &#125;,</span><br><span class="line">                (consumerTag) -&gt; System.out.println(<span class="string">&quot;消费者取消接收消息&quot;</span>+consumerTag)</span><br><span class="line">                );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>消费者2的代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Consumer02</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TASK_QUEUE_NAME = <span class="string">&quot;ack_queue&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;C2等待接收处理消息时间较长&quot;</span>);</span><br><span class="line">        channel.basicConsume(TASK_QUEUE_NAME,<span class="keyword">false</span>,</span><br><span class="line">                (consumerTag,delivery) -&gt; &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        TimeUnit.SECONDS.sleep(<span class="number">30</span>);</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                        e.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line">                    System.out.println(<span class="string">&quot;接收到消息:&quot;</span>+<span class="keyword">new</span> String(delivery.getBody(), StandardCharsets.UTF_8));</span><br><span class="line">                    <span class="comment">//1.消息的标记tag  2.false，不进行批量处理</span></span><br><span class="line">                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(),<span class="keyword">false</span>);</span><br><span class="line">                &#125;,</span><br><span class="line">                (consumerTag) -&gt; System.out.println(<span class="string">&quot;消费者取消接收消息&quot;</span>+consumerTag)</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><font color='red'>消费者1和消费者2的区别：</font></p>
<p>它们的睡眠时间不同，这意味着它们确认消息的时间不同，消费者1可以很快的应答，而消费者2等待的时间较长。当启动两个消费者时，它们会以轮询的方式消费，但是消费者回应的速度很慢，在睡眠的过程中消费者2宕机，消息会重新入队，让消费者1消费。</p>
<p>可以比喻为，C1和C2是警察，他们都有各自的任务，C1执行的速度快，而C2虽然接收了n个消息，但是执行速度慢，选择离职，此时，只有C1这名警察，那么他要将C2未完成的任务一并完成。</p>
<h2 id="3-RabbitMQ持久化"><a href="#3-RabbitMQ持久化" class="headerlink" title="3.RabbitMQ持久化"></a>3.RabbitMQ持久化</h2><h3 id="3-1-概念"><a href="#3-1-概念" class="headerlink" title="3.1.概念"></a>3.1.概念</h3><p>默认情况下RabbitMQ退出或由于某种原因崩溃时，它忽视队列和消息，除非告知它不要这样做。<strong>确保消息不会丢失需要做两件事：需要将队列和消息都标记为持久化。</strong></p>
<h3 id="3-2队列如何实现持久化"><a href="#3-2队列如何实现持久化" class="headerlink" title="3.2队列如何实现持久化"></a>3.2队列如何实现持久化</h3><p>我们之前创建的队列都是非持久化的，rabbitmq如果重启的话，该队列就会被删除掉，如果要队列<strong>实现持久化</strong>，需要在声明队列的时候<strong>把durable参数设置为true</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.queueDeclare(QUEUE_NAME,durable,exclusive,autoDelete,arguments);</span><br></pre></td></tr></table></figure>

<p>如果之前的队列不是持久化的，需要先将原先的队列删除，如何重新创建持久化的队列，否则会报错</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201202025022.png" alt="image-20220120202556610"></p>
<p>持久化和非持久化队列的UI显示区：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201202024483.png" alt="image-20220120202401085"></p>
<h3 id="3-3消息实现持久化"><a href="#3-3消息实现持久化" class="headerlink" title="3.3消息实现持久化"></a>3.3消息实现持久化</h3><p>要让消息实现持久化需要在消息生产者修改代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.basicPublish(exchange,queueName,properties,body);</span><br></pre></td></tr></table></figure>

<p><font color='red'><strong>properties填写MessageProperties.PERSISTENT_TEXT_PLAIN,表示消息持久化</strong></font></p>
<p>将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉 RabbitMQ 将消息保存到磁盘，但是这里依然存在当消息刚准备存储在磁盘的时候 但是还没有存储完，消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强，但是对于我们的简单任务队列而言，这已经绰绰有余了。</p>
<p><font color='blue'>队列·持久化和消息持久化不同</font></p>
<h3 id="3-4不公平分发-建议使用"><a href="#3-4不公平分发-建议使用" class="headerlink" title="3.4不公平分发(建议使用)"></a>3.4不公平分发(建议使用)</h3><p>比方说有两个消费者在处理任务，消费者1处理消息快，消费者2处理消息慢，此时若是采用默认的轮询分发，就会导致消费者1很大部分时间处于空闲状态，而消费者2一直工作。</p>
<p>为了避免这种情况，可以采取以下策略（<font color='red'>由消费方设置</font>）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.basicQos(<span class="number">1</span>);  <span class="comment">//表示采用不公平分发，0表示默认情况：轮询</span></span><br></pre></td></tr></table></figure>

<p>遵循的原则：能者多劳</p>
<h3 id="3-5预取值"><a href="#3-5预取值" class="headerlink" title="3.5预取值"></a>3.5预取值</h3><p>本身消息的发送就是异步发送的，所以在任何时候，channel 上肯定不止只有一个消息另外来自消费者的手动确认本质上也是异步的。因此这里就存在一个未确认的消息缓冲区，因此希望开发人员能<strong>限制此缓冲区的大小，以避免缓冲区里面无限制的未确认消息问题</strong>。这个时候就可以通过使用 basic.qos 方法设置“预取计数”值来完成的。<strong>该值定义通道上允许的未确认消息的最大数量</strong>。一旦数量达到配置的数量，RabbitMQ 将停止在通道上传递更多消息，除非至少有一个未处理的消息被确认，例如，假设在通道上有未确认的消息 5、6、7，8，并且通道的预取计数设置为 4，此时 RabbitMQ 将不会在该通道上再传递任何消息，除非至少有一个未应答的消息被 ack。比方说 tag=6 这个消息刚刚被确认 ACK，RabbitMQ 将会感知这个情况到并再发送一条消息。消息应答和 QoS 预取值对用户吞吐量有重大影响。通常，增加预取将提高向消费者传递消息的速度。<strong>虽然自动应答传输消息速率是最佳的，但是，在这种情况下已传递但尚未处理的消息的数量也会增加，从而增加了消费者的RAM</strong> <strong>消耗</strong>(随机存取存储器)应该小心使用具有无限预处理的自动确认模式或手动确认模式，消费者消费了大量的消息如果没有确认的话，会导致消费者连接节点的内存消耗变大，所以找到合适的预取值是一个反复试验的过程，不同的负载该值取值也不同 100 到 300 范围内的值通常可提供最佳的吞吐量，并且不会给消费者带来太大的风险。预取值为 1 是最保守的。当然这将使吞吐量变得很低，特别是消费者连接延迟很严重的情况下，特别是在消费者连接等待时间较长的环境中。对于大多数应用来说，稍微高一点的值将是最佳的。</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201211539272.png" alt="image-20220121153910625"></p>
<p><font color='red'>通俗易懂的说法就是prefetch表示那个消费者能够接收的任务数量，达到上限后，只有完成目标回复ack，确认完成后，才可继续接收任务</font></p>
<p>prefetch就是预取值</p>
<h1 id="五、发布确认"><a href="#五、发布确认" class="headerlink" title="五、发布确认"></a>五、发布确认</h1><h2 id="1-发布确认原理"><a href="#1-发布确认原理" class="headerlink" title="1. 发布确认原理"></a>1. 发布确认原理</h2><p>生产者将信道设置成 confirm 模式，一旦信道进入 confirm 模式，<strong>所有在该信道上面发布的消息都将会被指派一个唯一的 ID</strong>(从 1 开始)，一旦消息被投递到所有匹配的队列之后，broker就会发送一个确认给生产者(包含消息的唯一 ID)，这就使得生产者知道消息已经正确到达目的队列了，如果消息和队列是可持久化的，那么确认消息会在将消息写入磁盘之后发出，broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号，此外, broker 也可以设置basic.ack 的 multiple 域，表示到这个序列号之前的所有消息都已经得到了处理。</p>
<p>confirm 模式最大的好处在于他是异步的，一旦发布一条消息，生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息，当消息最终得到确认之后，生产者应用便可以通过回调方法来处理该确认消息，如果 RabbitMQ 因为自身内部错误导致消息丢失，就会发送一条 nack 消息，生产者应用程序同样可以在回调方法中处理该 nack 消息队列持久化和消息持久化并不能完全保证消息不丢失，需要发布确认才能保证消息的完整性</p>
<h2 id="2-发布确认策略"><a href="#2-发布确认策略" class="headerlink" title="2.发布确认策略"></a>2.发布确认策略</h2><h3 id="2-1-开启发布确认的方法"><a href="#2-1-开启发布确认的方法" class="headerlink" title="2.1.开启发布确认的方法"></a>2.1.开启发布确认的方法</h3><p>发布确认默认是没有开启的，如果要开启需要调用方法 confirmSelect，每当你要想使用发布</p>
<p>确认，都需要在 channel 上调用该方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Channel channel = RabbitMQUtils.getChannel(HOST,<span class="string">&quot;admin&quot;</span>,<span class="string">&quot;123&quot;</span>);</span><br><span class="line"><span class="comment">//开启发布确认</span></span><br><span class="line">channel.confirmSelect();</span><br></pre></td></tr></table></figure>

<h3 id="2-2-单个确认发布"><a href="#2-2-单个确认发布" class="headerlink" title="2.2. 单个确认发布"></a>2.2. 单个确认发布</h3><p>这是一种简单的确认方式，它是一种<strong>同步确认发布</strong>的方式，也就是发布一个消息之后只有它被确认发布，后续的消息才能继续发布,waitForConfirmsOrDie(long)这个方法只有在消息被确认的时候才返回，如果在指定时间范围内这个消息没有被确认那么它将抛出异常。</p>
<p>这种确认方式有一个最大的缺点就是:<strong>发布速度特别的慢，</strong>因为如果没有确认发布的消息就会阻塞所有后续消息的发布，这种方式最多提供每秒不超过数百条发布消息的吞吐量。当然对于某些应用程序来说这可能已经足够了。</p>
<p>代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//单个确认</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">publishMessageIndividually</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">    <span class="comment">//声明队列</span></span><br><span class="line">    String queueName = UUID.randomUUID().toString();</span><br><span class="line">    channel.queueDeclare(queueName,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">    <span class="comment">//开启发布确认</span></span><br><span class="line">    channel.confirmSelect();</span><br><span class="line">    <span class="comment">//开启时间</span></span><br><span class="line">    <span class="keyword">long</span> begin = System.currentTimeMillis();</span><br><span class="line">    <span class="comment">//批量发消息</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; MESSAGE_COUNT; i++) &#123;</span><br><span class="line">        String message =<span class="string">&quot;&quot;</span> + i;</span><br><span class="line">        channel.basicPublish(<span class="string">&quot;&quot;</span>,queueName,<span class="keyword">false</span>,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">//单个消息就马上进行发布确认</span></span><br><span class="line">        <span class="keyword">boolean</span> flag = channel.waitForConfirms();</span><br><span class="line">        <span class="keyword">if</span>(flag)&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息发送成功&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息发送失败&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">&quot;总耗时:&quot;</span>+(end-begin));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="2-3-批量确认发布"><a href="#2-3-批量确认发布" class="headerlink" title="2.3. 批量确认发布"></a>2.3. 批量确认发布</h3><p>上面那种方式非常慢，与单个等待确认消息相比，先发布一批消息然后一起确认可以极大地提高吞吐量，当然这种方式的<strong>缺点</strong>就是:<strong>当发生故障导致发布出现问题时，不知道是哪个消息出现问题了</strong>，我们必须将整个批处理保存在内存中，以记录重要的信息而后重新发布消息。当然这种方案仍然是<strong>同步的，也一样阻塞消息的发布。</strong></p>
<p>代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//批量确认</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">publishMessageBatch</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">    channel.confirmSelect();</span><br><span class="line">    String queueName = UUID.randomUUID().toString();</span><br><span class="line">    <span class="comment">//声明队列</span></span><br><span class="line">    channel.queueDeclare(queueName,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">    <span class="comment">//批量处理的数量</span></span><br><span class="line">    <span class="keyword">int</span> batchSize = <span class="number">10</span>;</span><br><span class="line">    <span class="comment">//未确认消息个数</span></span><br><span class="line">    <span class="keyword">int</span> outstandingMessageCount = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">long</span> begin = System.currentTimeMillis();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; MESSAGE_COUNT; i++) &#123;</span><br><span class="line">        String message = <span class="string">&quot;&quot;</span>+i;</span><br><span class="line">        channel.basicPublish(<span class="string">&quot;&quot;</span>,queueName,<span class="keyword">null</span>,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">//未确认个数+1</span></span><br><span class="line">        outstandingMessageCount++;</span><br><span class="line">        <span class="keyword">if</span>(outstandingMessageCount == batchSize)&#123;</span><br><span class="line">            <span class="comment">//达到批量处理数量，便等待确认</span></span><br><span class="line">            channel.waitForConfirms();</span><br><span class="line">            <span class="comment">//重新置为0</span></span><br><span class="line">            outstandingMessageCount = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(outstandingMessageCount &gt; <span class="number">0</span>)&#123;</span><br><span class="line">        channel.waitForConfirms();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">&quot;耗时:&quot;</span>+(end-begin));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><font color='red'>弊端：无法确认哪个消息没有被确认</font></p>
<h3 id="2-4异步确认发布"><a href="#2-4异步确认发布" class="headerlink" title="2.4异步确认发布"></a>2.4异步确认发布</h3><p>异步确认虽然编程逻辑比上两个要复杂，但是<strong>性价比最高</strong>，无论是可靠性还是效率都没得说，他是利用回调函数来达到消息可靠性传递的，这个中间件也是通过函数回调来保证是否投递成功</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201221743004.png" alt="image-20220122174304913"></p>
<p>代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//异步发布确认</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">publishMessageAsync</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">    channel.confirmSelect();</span><br><span class="line">    String queueName = UUID.randomUUID().toString();</span><br><span class="line">    channel.queueDeclare(queueName,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">    <span class="comment">//开始时间</span></span><br><span class="line">    <span class="keyword">long</span> begin = System.currentTimeMillis();</span><br><span class="line">    <span class="comment">//准备消息的监听器 监听哪些消息成功，哪些消息失败</span></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 参数的含义:</span></span><br><span class="line"><span class="comment">         * 1.消息的标记</span></span><br><span class="line"><span class="comment">         * 2.是否为批量确认</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">    channel.addConfirmListener(</span><br><span class="line">        <span class="comment">//消息确认成功 回调函数</span></span><br><span class="line">        (deliveryTag,multiple)-&gt;&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;确认消息的编号:&quot;</span>+deliveryTag);</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="comment">//消息确认失败 回调函数</span></span><br><span class="line">        (deliveryTag, multiple) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;未确认的消息:&quot;</span>+deliveryTag);</span><br><span class="line">        &#125;</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; MESSAGE_COUNT; i++) &#123;</span><br><span class="line">        String message = <span class="string">&quot;&quot;</span> + i;</span><br><span class="line">        channel.basicPublish(<span class="string">&quot;&quot;</span>,queueName,<span class="keyword">null</span>,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//结束时间</span></span><br><span class="line">    <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">&quot;耗时:&quot;</span>+(end-begin));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="2-5如何处理异步未确认消息"><a href="#2-5如何处理异步未确认消息" class="headerlink" title="2.5如何处理异步未确认消息"></a>2.5如何处理异步未确认消息</h3><p>最好的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列，比如说用 <strong>ConcurrentLinkedQueue</strong> 这个队列在 confirm callbacks 与发布线程之间进行消息的传递。</p>
<p>代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//异步发布确认</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">publishMessageAsync</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">    channel.confirmSelect();</span><br><span class="line">    String queueName = UUID.randomUUID().toString();</span><br><span class="line">    channel.queueDeclare(queueName,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">        * 线程安全有序的一个哈希表,适用于高并发的情况</span></span><br><span class="line"><span class="comment">        * 1.轻松的将序号与消息进行关联</span></span><br><span class="line"><span class="comment">        * 2.轻松批量删除条目 只要给序号</span></span><br><span class="line"><span class="comment">        * 3.支持高并发</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    ConcurrentSkipListMap&lt;Long,String&gt; outstandingConfirms = <span class="keyword">new</span> ConcurrentSkipListMap&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//准备消息的监听器 监听哪些消息成功，哪些消息失败</span></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">        * 参数的含义:</span></span><br><span class="line"><span class="comment">        * 1.消息的标记</span></span><br><span class="line"><span class="comment">        * 2.是否为批量确认</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    channel.addConfirmListener(</span><br><span class="line">        <span class="comment">//消息确认成功 回调函数</span></span><br><span class="line">        (deliveryTag,multiple)-&gt;&#123;</span><br><span class="line">            <span class="comment">//2.删除已经确认的消息，剩下的就是未确认的消息</span></span><br><span class="line">            System.out.println(<span class="string">&quot;multiple=&quot;</span>+multiple);</span><br><span class="line">            <span class="keyword">if</span>(multiple)&#123;</span><br><span class="line">                ConcurrentNavigableMap&lt;Long, String&gt; confirmed = outstandingConfirms.headMap(deliveryTag);</span><br><span class="line">                confirmed.clear();</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                outstandingConfirms.remove(deliveryTag);</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(<span class="string">&quot;确认消息的编号:&quot;</span>+deliveryTag);</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="comment">//消息确认失败 回调函数</span></span><br><span class="line">        (deliveryTag, multiple) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;未确认的消息:&quot;</span>+deliveryTag);</span><br><span class="line">        &#125;</span><br><span class="line">    );</span><br><span class="line">    <span class="comment">//开始时间</span></span><br><span class="line">    <span class="keyword">long</span> begin = System.currentTimeMillis();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; MESSAGE_COUNT; i++) &#123;</span><br><span class="line">        String message = <span class="string">&quot;&quot;</span> + i;</span><br><span class="line">        channel.basicPublish(<span class="string">&quot;&quot;</span>,queueName,<span class="keyword">null</span>,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">//1.记录所有要发送的消息</span></span><br><span class="line">        outstandingConfirms.put(channel.getNextPublishSeqNo(),message);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//结束时间</span></span><br><span class="line">    <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">&quot;耗时:&quot;</span>+(end-begin));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="六、交换机"><a href="#六、交换机" class="headerlink" title="六、交换机"></a>六、交换机</h2><p>将消息传达给多个消费者。这种模式称为 ”发布/订阅”.</p>
<p>为了说明这种模式，我们将构建一个简单的日志系统。它将由两个程序组成:第一个程序将发出日志消息，第二个程序是消费者。其中我们会启动两个消费者，其中一个消费者<strong>接收到消息后把日志存储在磁盘</strong>，另外一个消费者<strong>接收到消息后把消息打印在屏幕上</strong>，事实上<strong>第一个程序发出的日志消息将广播给所有消费者</strong></p>
<h2 id="1-Exchange"><a href="#1-Exchange" class="headerlink" title="1.Exchange"></a>1.Exchange</h2><h3 id="1-1-Exchange概念"><a href="#1-1-Exchange概念" class="headerlink" title="1.1.Exchange概念"></a>1.1.Exchange概念</h3><p>RabbitMQ 消息传递模型的核心思想是: <strong>生产者生产的消息从不会直接发送到队列</strong>。实际上，通常生产者甚至都不知道这些消息传递到了哪些队列中。</p>
<p>相反，<strong>生产者只能将消息发送到交换机(exchange)<strong>，交换机工作的内容非常简单，一方面它接收来自生产者的消息，另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列，还是说把他们放到许多队列中，还是说应该丢弃它们。这就</strong>由交换机的类型来决定。</strong></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201222219029.png" alt="image-20220122221935988"></p>
<p>以前我们用的都是<strong>工作模式Work queues</strong></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201222303110.png" alt="image-20220122230325839"></p>
<p>而Publish/Subscribe模式如下:</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201222304977.png" alt="image-20220122230446975"></p>
<h3 id="1-2-Exchange的类型"><a href="#1-2-Exchange的类型" class="headerlink" title="1.2.Exchange的类型"></a>1.2.Exchange的类型</h3><p>总共有以下类型：</p>
<p>直接(direct), 主题(topic) ,标题(headers) , 扇出(fanout)</p>
<h3 id="1-3-无名exchange"><a href="#1-3-无名exchange" class="headerlink" title="1.3.无名exchange"></a>1.3.无名exchange</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.basicPublish(<span class="string">&quot;&quot;</span>,routingKey,pros,bytes);</span><br></pre></td></tr></table></figure>

<p>我们<strong>以””空串的形式表示默认（direct）</strong>交换或无名称交换机；消息能路由发送到队列中，其实是由routingKey(bindingKey)绑定key指定的，如果它存在的话</p>
<h2 id="2-临时队列"><a href="#2-临时队列" class="headerlink" title="2.临时队列"></a>2.临时队列</h2><p>每当我们连接到RabbitMQ的时候，我们都需要一个全新的空队列，为此我们可以创建出一个具有<strong>随机名称的队列</strong>，或者让服务器为我们选择一个随机队列名称。<strong>一旦我们断开消费者的连接，队列将被自动删除</strong></p>
<p>创建临时队列的方式如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String queueName = channel.queueDeclare().getQueue();</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241451806.png" alt="image-20220124145145628"></p>
<p>此时我们断开消费者的连接，临时队列就会删除</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241452050.png" alt="image-20220124145227716"></p>
<h2 id="3-绑定（bindings）"><a href="#3-绑定（bindings）" class="headerlink" title="3.绑定（bindings）"></a>3.绑定（bindings）</h2><p>绑定就是exchange和queue之间的桥梁，它告诉我们exchange和哪个队列进行绑定关系。</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241434081.png" alt="image-20220124143447729"></p>
<p>routingKey的作用：区别对待，保证交换机可以将某条消息发送给某个特定的队列或某些队列</p>
<h2 id="4-Fanout"><a href="#4-Fanout" class="headerlink" title="4.Fanout"></a>4.Fanout</h2><h3 id="4-1-Fanout介绍"><a href="#4-1-Fanout介绍" class="headerlink" title="4.1.Fanout介绍"></a>4.1.Fanout介绍</h3><p>Fanout这种类型非常简单。它是将接收到的所有消息广播到它知道的所有队列中。系统中有fanout类型的交换机</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241432395.png" alt="image-20220124143248654"></p>
<h3 id="4-2-Fanout实战"><a href="#4-2-Fanout实战" class="headerlink" title="4.2.Fanout实战"></a>4.2.Fanout实战</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241435659.png" alt="image-20220124143519050"></p>
<p>ReceiveLogs01将接收到的消息打印再控制台</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReceiveLogs01</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="comment">//交换机的名称</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;logs&quot;</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//声明交换机</span></span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME,<span class="string">&quot;fanout&quot;</span>);</span><br><span class="line">        <span class="comment">//声明队列</span></span><br><span class="line">        <span class="comment">//临时队列，当消费者断开与队列的连接时，队列自动删除</span></span><br><span class="line">        String queueName = channel.queueDeclare().getQueue();</span><br><span class="line">        <span class="comment">//绑定交换机与队列</span></span><br><span class="line">        channel.queueBind(queueName,EXCHANGE_NAME,<span class="string">&quot;&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;等待接收消息，把接收到的消息打印再屏幕上&quot;</span>);</span><br><span class="line">        <span class="comment">//接收消息</span></span><br><span class="line">        channel.basicConsume(queueName,<span class="keyword">true</span>,(consumerTag,message) -&gt; &#123;</span><br><span class="line">                    System.out.println(<span class="string">&quot;ReceiveLogs01接收到消息:&quot;</span>+<span class="keyword">new</span> String(message.getBody()));</span><br><span class="line">        &#125;,(consumerTag) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息取消消费回调&#123;&#125;&quot;</span>+consumerTag);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Receivelogs02消息也打印在控制台</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReceiveLogs02</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="comment">//交换机的名称</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;logs&quot;</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//声明交换机</span></span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME,<span class="string">&quot;fanout&quot;</span>);</span><br><span class="line">        <span class="comment">//声明队列</span></span><br><span class="line">        <span class="comment">//临时队列，当消费者断开与队列的连接时，队列自动删除</span></span><br><span class="line">        String queueName = channel.queueDeclare().getQueue();</span><br><span class="line">        <span class="comment">//绑定交换机与队列</span></span><br><span class="line">        channel.queueBind(queueName,EXCHANGE_NAME,<span class="string">&quot;&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;等待接收消息，把接收到的消息打印再屏幕上&quot;</span>);</span><br><span class="line">        <span class="comment">//接收消息</span></span><br><span class="line">        channel.basicConsume(queueName,<span class="keyword">true</span>,(consumerTag,message) -&gt; &#123;</span><br><span class="line">                    System.out.println(<span class="string">&quot;ReceiveLogs02接收到消息:&quot;</span>+<span class="keyword">new</span> String(message.getBody()));</span><br><span class="line">        &#125;,(consumerTag) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息取消消费回调&#123;&#125;&quot;</span>+consumerTag);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>EmitLog发送消息</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EmitLog</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="comment">//交换机的名称</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;logs&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME,<span class="string">&quot;fanout&quot;</span>);</span><br><span class="line">        Scanner sc = <span class="keyword">new</span> Scanner(System.in);</span><br><span class="line">        <span class="keyword">while</span>(sc.hasNext())&#123;</span><br><span class="line">            String message = <span class="string">&quot;&quot;</span>+sc.next();</span><br><span class="line">            <span class="comment">//发送消息</span></span><br><span class="line">    		channel.basicPublish(EXCHANGE_NAME,<span class="string">&quot;&quot;</span>,<span class="keyword">null</span>,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">            System.out.println(<span class="string">&quot;生产者发出消息:&quot;</span>+message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="5-Direct-exchange"><a href="#5-Direct-exchange" class="headerlink" title="5.Direct exchange"></a>5.Direct exchange</h2><h3 id="5-1-Direct-exchange介绍"><a href="#5-1-Direct-exchange介绍" class="headerlink" title="5.1.Direct exchange介绍"></a>5.1.Direct exchange介绍</h3><p>Fanout这种交换类型并不能给我们带来很大的灵活性，它只能进行无意识的广播，而使用direct类型，可以使消息到它绑定的routingKey队列中去。例如我们希望将日志消息写入磁盘的程序仅接收错误消息（error），而不存储那些警告或信息日志消息，避免浪费磁盘空间。</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241720559.png" alt="image-20220124172026122"></p>
<p>在上面这张图中，我们可以看到 X 绑定了两个队列，绑定类型是 direct。队列 Q1 绑定键为 orange，队列 Q2 绑定键有两个:一个绑定键为 black，另一个绑定键为 green.</p>
<p>在这种绑定情况下，生产者发布消息到 exchange 上，绑定键为 orange 的消息会被发布到队列Q1。绑定键为 black或green 和的消息会被发布到队列 Q2，其他消息类型的消息将被丢弃</p>
<h3 id="5-2-多重绑定"><a href="#5-2-多重绑定" class="headerlink" title="5.2.多重绑定"></a>5.2.多重绑定</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241721055.png" alt="image-20220124172155331"></p>
<p>当然如果 exchange 的绑定类型是 direct，<strong>但是它绑定的多个队列的</strong> <strong>key</strong> <strong>如果都相同</strong>，在这种情况下虽然绑定类型是 direct <strong>但是它表现的就和</strong> <strong>fanout</strong> <strong>有点类似了</strong>，就跟广播差不多。</p>
<h3 id="5-3-实战"><a href="#5-3-实战" class="headerlink" title="5.3.实战"></a>5.3.实战</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241724425.png" alt="image-20220124172404833"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201241724084.png" alt="image-20220124172426073"></p>
<p>队列1代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReceiveLogsDirect01</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="comment">//交换机名称</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;direct_logs&quot;</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//声明一个交换机</span></span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);</span><br><span class="line">        String queueName = <span class="string">&quot;console&quot;</span>;</span><br><span class="line">        <span class="comment">//声明一个队列</span></span><br><span class="line">        channel.queueDeclare(queueName,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(queueName,EXCHANGE_NAME,<span class="string">&quot;info&quot;</span>);</span><br><span class="line">        channel.queueBind(queueName,EXCHANGE_NAME,<span class="string">&quot;waring&quot;</span>);</span><br><span class="line">        channel.basicConsume(queueName,<span class="keyword">true</span>,(consumerTag, message) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;ReceiveLogsDirect01接收到消息:&quot;</span>+<span class="keyword">new</span> String(message.getBody()));</span><br><span class="line">        &#125;,(consumerTag -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息取消消费回调&#123;&#125;&quot;</span>+consumerTag);</span><br><span class="line">        &#125;));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>队列2代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReceiveLogsDirect02</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="comment">//交换机名称</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;direct_logs&quot;</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//声明一个交换机</span></span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);</span><br><span class="line">        String queueName = <span class="string">&quot;disk&quot;</span>;</span><br><span class="line">        <span class="comment">//声明一个队列</span></span><br><span class="line">        channel.queueDeclare(queueName,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(queueName,EXCHANGE_NAME,<span class="string">&quot;error&quot;</span>);</span><br><span class="line">        channel.basicConsume(queueName,<span class="keyword">true</span>,(consumerTag, message) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;ReceiveLogsDirect02接收到消息:&quot;</span>+<span class="keyword">new</span> String(message.getBody()));</span><br><span class="line">        &#125;,(consumerTag -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息取消消费回调&#123;&#125;&quot;</span>+consumerTag);</span><br><span class="line">        &#125;));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>发布消息：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DirectLogs</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="comment">//交换机名称</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;direct_logs&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        Scanner sc = <span class="keyword">new</span> Scanner(System.in);</span><br><span class="line">        String routingKey = <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">while</span>(sc.hasNext())&#123;</span><br><span class="line">            String message = sc.next();</span><br><span class="line">            <span class="keyword">if</span>(message.contains(<span class="string">&quot;error&quot;</span>))&#123;</span><br><span class="line">                routingKey = <span class="string">&quot;error&quot;</span>;</span><br><span class="line">                System.out.println(<span class="string">&quot;消息错误&quot;</span>);</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                routingKey = <span class="string">&quot;info&quot;</span>;</span><br><span class="line">                System.out.println(<span class="string">&quot;正常发送&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            channel.basicPublish(EXCHANGE_NAME,routingKey,<span class="keyword">null</span>,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><font color='red'>消息的发送会根据routingKey，发送到对应的队列中。</font></p>
<h2 id="6-Topic"><a href="#6-Topic" class="headerlink" title="6.Topic"></a>6.Topic</h2><h3 id="6-1-direct类型存在的局限性"><a href="#6-1-direct类型存在的局限性" class="headerlink" title="6.1.direct类型存在的局限性"></a>6.1.direct类型存在的局限性</h3><p>尽管使用 direct 交换机改进了我们的系统，但是它仍然存在局限性-比方说我们想接收的日志类型有info.base 和 info.advantage，某个队列只想 info.base 的消息，那这个时候 direct 就办不到了。这个时候就只能使用 topic 类型</p>
<h3 id="6-2-Topic的要求"><a href="#6-2-Topic的要求" class="headerlink" title="6.2.Topic的要求"></a>6.2.Topic的要求</h3><p>发送到类型是 topic 交换机的消息的 routingKey 不能随意写，必须满足一定的要求，它<strong>必须是一个单词列表，以点号分隔开</strong>。这些单词可以是任意单词，比如说：”stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”.这种类型的。当然这个单词列表最多不能超过 255 个字节。</p>
<p>在这个规则列表中，其中有两个替换符是大家需要注意的</p>
<p>***(星号)可以代替  一个单词**</p>
<p><strong>#(井号)可以替代  零个或多个单词</strong></p>
<h3 id="6-3-Topic匹配案例"><a href="#6-3-Topic匹配案例" class="headerlink" title="6.3.Topic匹配案例"></a>6.3.Topic匹配案例</h3><p>下图绑定关系如下</p>
<ul>
<li><p>Q1–&gt;绑定的是</p>
<ul>
<li> 中间带 orange 带 3 个单词的字符串(<em>.orange.</em>)</li>
</ul>
</li>
<li><p>Q2–&gt;绑定的是</p>
<ul>
<li>最后一个单词是 rabbit 的 3 个单词(<em>.</em>.rabbit)</li>
<li>第一个单词是 lazy 的多个单词(lazy.#)</li>
</ul>
</li>
</ul>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201260102538.png" alt="image-20220126010239937"></p>
<p><font color='red'><strong>当一个队列绑定键是#,那么这个队列将接收所有数据，就有点像 fanout 了。如果队列绑定键当中没有#和*出现，那么该队列绑定类型就是direct了</strong></font></p>
<h3 id="6-4-实战"><a href="#6-4-实战" class="headerlink" title="6.4.实战"></a>6.4.实战</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201260105694.png" alt="image-20220126010520330"></p>
<p>队列1代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReceiveLogsTopic01</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;topic_logs&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//声明交换机</span></span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);</span><br><span class="line">        <span class="comment">//创建临时队列</span></span><br><span class="line">        String queueName = channel.queueDeclare().getQueue();</span><br><span class="line">        <span class="comment">//绑定队列和交换机</span></span><br><span class="line">        channel.queueBind(queueName,EXCHANGE_NAME,<span class="string">&quot;*.orange.*&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;ReceiveLogsTopic01等待接收消息&quot;</span>);</span><br><span class="line">        channel.basicConsume(queueName,<span class="keyword">true</span>,(consumerTag,delivery) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;接收到消息:&quot;</span>+<span class="keyword">new</span> String(delivery.getBody())+<span class="string">&quot;,绑定键:&quot;</span>+delivery.getEnvelope().getRoutingKey());</span><br><span class="line">        &#125;,(consumerTag -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息取消消费&#123;&#125;&quot;</span>+consumerTag);</span><br><span class="line">        &#125;));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>队列2代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReceiveLogsTopic01</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;topic_logs&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//声明交换机</span></span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);</span><br><span class="line">        <span class="comment">//创建临时队列</span></span><br><span class="line">        String queueName = channel.queueDeclare().getQueue();</span><br><span class="line">        <span class="comment">//绑定队列和交换机</span></span><br><span class="line">        channel.queueBind(queueName,EXCHANGE_NAME,<span class="string">&quot;*.orange.*&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;ReceiveLogsTopic01等待接收消息&quot;</span>);</span><br><span class="line">        channel.basicConsume(queueName,<span class="keyword">true</span>,(consumerTag,delivery) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;接收到消息:&quot;</span>+<span class="keyword">new</span> String(delivery.getBody())+<span class="string">&quot;,绑定键:&quot;</span>+delivery.getEnvelope().getRoutingKey());</span><br><span class="line">        &#125;,(consumerTag -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息取消消费&#123;&#125;&quot;</span>+consumerTag);</span><br><span class="line">        &#125;));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发送消息：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EmitLogsTopic</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">&quot;topic_logs&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);</span><br><span class="line">        Map&lt;String,String&gt; bindingKeyMap = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        bindingKeyMap.put(<span class="string">&quot;quick.orange.rabbit&quot;</span>,<span class="string">&quot;被队列 Q1Q2 接收到&quot;</span>);</span><br><span class="line">        bindingKeyMap.put(<span class="string">&quot;lazy.orange.elephant&quot;</span>,<span class="string">&quot;被队列 Q1Q2 接收到&quot;</span>);</span><br><span class="line">        bindingKeyMap.put(<span class="string">&quot;quick.orange.fox&quot;</span>,<span class="string">&quot;被队列 Q1 接收到&quot;</span>);</span><br><span class="line">        bindingKeyMap.put(<span class="string">&quot;lazy.brown.fox&quot;</span>,<span class="string">&quot;被队列 Q2 接收到&quot;</span>);</span><br><span class="line">        bindingKeyMap.put(<span class="string">&quot;lazy.pink.rabbit&quot;</span>,<span class="string">&quot;虽然满足两个绑定但只被队列 Q2 接收一次&quot;</span>);</span><br><span class="line">        bindingKeyMap.put(<span class="string">&quot;quick.brown.fox&quot;</span>,<span class="string">&quot;不匹配任何绑定不会被任何队列接收到会被丢弃&quot;</span>);</span><br><span class="line">        bindingKeyMap.put(<span class="string">&quot;quick.orange.male.rabbit&quot;</span>,<span class="string">&quot;是四个单词不匹配任何绑定会被丢弃&quot;</span>);</span><br><span class="line">        bindingKeyMap.put(<span class="string">&quot;lazy.orange.male.rabbit&quot;</span>,<span class="string">&quot;是四个单词但匹配 Q2&quot;</span>);</span><br><span class="line">        <span class="keyword">for</span> (String key : bindingKeyMap.keySet()) &#123;</span><br><span class="line">            channel.basicPublish(EXCHANGE_NAME,key,<span class="keyword">null</span>,bindingKeyMap.get(key).getBytes(StandardCharsets.UTF_8));</span><br><span class="line">            System.out.println(<span class="string">&quot;生产消息:&quot;</span>+bindingKeyMap.get(key));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="七、死信队列"><a href="#七、死信队列" class="headerlink" title="七、死信队列"></a>七、死信队列</h1><h2 id="7-1死信的概念"><a href="#7-1死信的概念" class="headerlink" title="7.1死信的概念"></a>7.1死信的概念</h2><p>一般情况下，producer将消息投递到broker或者直接到queue里，consumer从queue取出西澳西进行消费，但某些时候由于特定的原因导致queue中的某些消息无法被消费，这样的消息如果没有后续的处理，就会变成死信，有死信自然就有死信队列。</p>
<h2 id="7-2应用场景"><a href="#7-2应用场景" class="headerlink" title="7.2应用场景"></a>7.2应用场景</h2><p>为了保证订单业务的消息数据不丢失，需要使用到 RabbitMQ 的死信队列机制，当消息消费发生异常时，将消息投入死信队列中。还有比如说: 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效</p>
<h2 id="7-3死信的来源"><a href="#7-3死信的来源" class="headerlink" title="7.3死信的来源"></a>7.3死信的来源</h2><p>消息TTL过期</p>
<p>队列达到最大长度(队列满了，无法再添加数据到 mq 中)</p>
<p>消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false</p>
<h2 id="7-4实战"><a href="#7-4实战" class="headerlink" title="7.4实战"></a>7.4实战</h2><h3 id="7-4-1-架构图"><a href="#7-4-1-架构图" class="headerlink" title="7.4.1.架构图"></a>7.4.1.架构图</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201262029897.png" alt="image-20220126202901176"></p>
<h3 id="7-4-2-模拟消息TTL过期制造死信"><a href="#7-4-2-模拟消息TTL过期制造死信" class="headerlink" title="7.4.2.模拟消息TTL过期制造死信"></a>7.4.2.模拟消息TTL过期制造死信</h3><p>生产者代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Producer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String NORMAL_EXCHANGE = <span class="string">&quot;normal_exchange&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//设置消息的TTL过期时间</span></span><br><span class="line">        AMQP.BasicProperties properties = <span class="keyword">new</span> AMQP.BasicProperties().builder().expiration(<span class="string">&quot;10000&quot;</span>).build();</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            String message = <span class="string">&quot;info&quot;</span>+i;</span><br><span class="line">            channel.basicPublish(NORMAL_EXCHANGE,<span class="string">&quot;zhangsan&quot;</span>,properties,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">            System.out.println(<span class="string">&quot;生产者发送消息:&quot;</span>+message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>消费者1代码：<font color='red'>(<strong>启动之后关闭该消费者 模拟其接收不到消息</strong>)</font></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Consumer01</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="comment">//普通交换机名称</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String NORMAL_EXCHANGE = <span class="string">&quot;normal_exchange&quot;</span>;</span><br><span class="line">    <span class="comment">//死信交换机名称</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DEAD_EXCHANGE = <span class="string">&quot;dead_exchange&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//声明普通和死信交换机，类型为direct</span></span><br><span class="line">        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);</span><br><span class="line">        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);</span><br><span class="line">        <span class="comment">//声明死信队列</span></span><br><span class="line">        String deadQueue = <span class="string">&quot;dead-queue&quot;</span>;</span><br><span class="line">        channel.queueDeclare(deadQueue,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">        <span class="comment">//绑定死信队列和死信交换机</span></span><br><span class="line">        channel.queueBind(deadQueue,DEAD_EXCHANGE,<span class="string">&quot;lisi&quot;</span>);</span><br><span class="line">        <span class="comment">//正常队列绑定死信队列消息</span></span><br><span class="line">        Map&lt;String,Object&gt; params = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        <span class="comment">//正常队列设置死信交换机，参数key是固定值</span></span><br><span class="line">        params.put(<span class="string">&quot;x-dead-letter-exchange&quot;</span>,DEAD_EXCHANGE);</span><br><span class="line">        <span class="comment">//正常队列设置死信routingKey，参数key是固定值</span></span><br><span class="line">        params.put(<span class="string">&quot;x-dead-letter-routing-key&quot;</span>,<span class="string">&quot;lisi&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//声明正常队列</span></span><br><span class="line">        String normalQueue = <span class="string">&quot;normal-queue&quot;</span>;</span><br><span class="line">        channel.queueDeclare(normalQueue,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,params);</span><br><span class="line">        <span class="comment">//绑定正常队列和交换机</span></span><br><span class="line">        channel.queueBind(normalQueue,NORMAL_EXCHANGE,<span class="string">&quot;zhangsan&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;等待接收消息...&quot;</span>);</span><br><span class="line">        <span class="comment">//正常队列消费消息</span></span><br><span class="line">        channel.basicConsume(normalQueue,<span class="keyword">true</span>,((consumerTag, message) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Consumer01接收到消息:&quot;</span>+<span class="keyword">new</span> String(message.getBody()));</span><br><span class="line">        &#125;),(consumerTag -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息取消消费&quot;</span>);</span><br><span class="line">        &#125;));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>生产者为发送消息时：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201262031567.png" alt="image-20220126203147507"></p>
<p>生产者发送10条消息，此时正常消息队列有10条</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201262122807.png" alt="image-20220126212223613"></p>
<p>10秒后若未消费，则消息会进入死信队列</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201262122624.png" alt="image-20220126212244520"></p>
<p>消费者2代码：(<strong>以上步骤完成后 启动 C2 消费者 它消费死信队列里面的消息</strong>) </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Consumer02</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DEAD_EXCHANGE = <span class="string">&quot;dead_exchange&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);</span><br><span class="line">        <span class="comment">//声明队列</span></span><br><span class="line">        String deadQueue = <span class="string">&quot;dead-queue&quot;</span>;</span><br><span class="line">        channel.queueDeclare(deadQueue,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line">        <span class="comment">//绑定队列和交换机</span></span><br><span class="line">        channel.queueBind(deadQueue,DEAD_EXCHANGE,<span class="string">&quot;lisi&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;等待接收死信队列的消息...&quot;</span>);</span><br><span class="line">        channel.basicConsume(deadQueue,<span class="keyword">true</span>,((consumerTag, message) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Consumer02消息死信队列的消息:&quot;</span>+<span class="keyword">new</span> String(message.getBody()));</span><br><span class="line">        &#125;),(consumerTag -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;消息取消消费回调&#123;&#125;&quot;</span>+consumerTag);</span><br><span class="line">        &#125;));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201262123362.png" alt="image-20220126212344345"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201262124825.png" alt="image-20220126212415690"></p>
<h3 id="7-4-3-模拟队列达到最大长度导致消息进入死信"><a href="#7-4-3-模拟队列达到最大长度导致消息进入死信" class="headerlink" title="7.4.3.模拟队列达到最大长度导致消息进入死信"></a>7.4.3.模拟队列达到最大长度导致消息进入死信</h3><p>1）将生产者中的TTL过期属性去掉</p>
<p>修正后代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Producer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String NORMAL_EXCHANGE = <span class="string">&quot;normal_exchange&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = RabbitMQUtils.getChannel(HOST, <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//1.设置消息的TTL过期时间</span></span><br><span class="line"><span class="comment">//        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration(&quot;10000&quot;).build();</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            String message = <span class="string">&quot;info&quot;</span>+i;</span><br><span class="line">            channel.basicPublish(NORMAL_EXCHANGE,<span class="string">&quot;zhangsan&quot;</span>,<span class="keyword">null</span>,message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">            System.out.println(<span class="string">&quot;生产者发送消息:&quot;</span>+message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>2）消费者1在声明队列时的扩展参数arguments增加对队列长度的限制（<strong>启动之后关闭该消费者 模拟其接收不到消息</strong>）</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201280112240.png" alt="image-20220128011222771"></p>
<p><font color='red'><strong>注意此时需要把原先队列删除 因为参数改变了</strong></font></p>
<p>3）消费者2代码无变化</p>
<p>生产者未发送消息</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201280015468.png" alt="image-20220128001507258"></p>
<p>生产者发送消息后，消费者1未消费，导致队列满，剩余消息进入死信队列</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201280018458.png" alt="image-20220128001834097"></p>
<p>启动消费者1和消费者2，将两个队列中的消息消费</p>
<h3 id="7-4-4-模拟消息被拒"><a href="#7-4-4-模拟消息被拒" class="headerlink" title="7.4.4.模拟消息被拒"></a>7.4.4.模拟消息被拒</h3><p>代码需要修改的地方：消费者1在接收消息时进行判断，如果未XXX消息就不接收</p>
<p><font color='red'>前提：需要开启消息的手动应答</font></p>
<p>消费者1消费消息的代码（部分代码）：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">boolean</span> autoAck = <span class="keyword">false</span>;</span><br><span class="line">channel.basicConsume(normalQueue, autoAck, (consumerTag, delivery) -&gt; &#123;</span><br><span class="line">    String message = <span class="keyword">new</span> String(delivery.getBody(), <span class="string">&quot;UTF-8&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span>(message.equals(<span class="string">&quot;info5&quot;</span>))&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Consumer01 接收到消息&quot;</span> + message + <span class="string">&quot;并拒绝签收该消息&quot;</span>);</span><br><span class="line">        <span class="comment">//requeue 设置为 false 代表拒绝重新入队 该队列如果配置了死信交换机将发送到死信队列中</span></span><br><span class="line">        channel.basicReject(delivery.getEnvelope().getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Consumer01 接收到消息&quot;</span>+message);</span><br><span class="line">        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, consumerTag -&gt; &#123;</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201280126762.png" alt="image-20220128012622073"></p>
<p>我们先启动消费者1，然后关闭，再启动生产者发送消息，我们看rabbitMQ的客户端可以看到正常队列有10条消息待消费，我们重新启动消费者1后，此时再看rabbitMQ的控制面板我们会发现，有一条消息进入了死信队列，这时为我们启动消费者2就可以消费这条消息</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201280131033.png" alt="image-20220128013107943"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201280127897.png" alt="image-20220128012727505"></p>
<h1 id="八、扩展（重点）"><a href="#八、扩展（重点）" class="headerlink" title="八、扩展（重点）"></a>八、扩展（重点）</h1><p>在声明队列时，有一个参数教arguments，它表示其它参数</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201280133003.png" alt="image-20220128013327490"></p>
<p>而这里扩展的就是关于这个参数到底可以写什么</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281541366.png" alt="image-20220128154145594"></p>
<p>从RabbitMQ的客户端界面可以看出，扩展的参数可以进行以上约束</p>
<h2 id="1-消息TTL"><a href="#1-消息TTL" class="headerlink" title="1.消息TTL"></a>1.消息TTL</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281544896.png" alt="image-20220128154421950"></p>
<h2 id="2-自动过期"><a href="#2-自动过期" class="headerlink" title="2.自动过期"></a>2.自动过期</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281545389.png" alt="image-20220128154546123"></p>
<h2 id="3-队列溢出行为"><a href="#3-队列溢出行为" class="headerlink" title="3.队列溢出行为"></a>3.队列溢出行为</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281547584.png" alt="image-20220128154748925"></p>
<ul>
<li><a target="_blank" rel="noopener" href="https://www.rabbitmq.com/quorum-queues.html">Quorum Queues</a>：一种现代的高可用复制队列类型</li>
</ul>
<p>使用溢出设置来配置队列溢出行为。如果 <strong><code>overflow</code></strong> 设置为 <strong><code>reject-publish</code>，则最近发布的消息将被丢弃。</strong>此外，如果 <a target="_blank" rel="noopener" href="https://www.rabbitmq.com/confirms.html#publisher-confirms">发布者确认</a> 已启用，将通过<code>basic.nack</code> 消息对发布者进行拒绝通知。如果一条消息被路由到多个队列并被其中至少一个队列拒绝，该信道将通过 <code>basic.nack</code> 通知发布者。该消息仍将被发布到可以将其排队的所有其他队列。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.queueDeclare(<span class="string">&quot;overflow&quot;</span>,<span class="string">&quot;reject-publish&quot;</span>);</span><br></pre></td></tr></table></figure>

<h2 id="4-单一活跃消费者"><a href="#4-单一活跃消费者" class="headerlink" title="4.单一活跃消费者"></a>4.单一活跃消费者</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281549044.png" alt="image-20220128154934788"></p>
<h2 id="5-死信交换"><a href="#5-死信交换" class="headerlink" title="5.死信交换"></a>5.死信交换</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281550920.png" alt="image-20220128155013623"></p>
<h2 id="6-死信路由键"><a href="#6-死信路由键" class="headerlink" title="6.死信路由键"></a>6.死信路由键</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281550676.png" alt="image-20220128155051105"></p>
<h2 id="7-设置队列最长长度"><a href="#7-设置队列最长长度" class="headerlink" title="7.设置队列最长长度"></a>7.设置队列最长长度</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281551174.png" alt="image-20220128155140009"></p>
<h2 id="8-最长长度字节"><a href="#8-最长长度字节" class="headerlink" title="8.最长长度字节"></a>8.最长长度字节</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281552448.png" alt="image-20220128155207453"></p>
<h2 id="9-最高优先级"><a href="#9-最高优先级" class="headerlink" title="9.最高优先级"></a>9.最高优先级</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281552387.png" alt="image-20220128155234262"></p>
<h2 id="10-懒惰模式"><a href="#10-懒惰模式" class="headerlink" title="10.懒惰模式"></a>10.懒惰模式</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281553896.png" alt="image-20220128155301852"></p>
<p>队列的两种模式：default 和 lazy</p>
<p>惰性队列的声明细节：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Map&lt;String, Object&gt; args = <span class="keyword">new</span> HashMap&lt;String, Object&gt;();</span><br><span class="line">args.put(<span class="string">&quot;x-queue-mode&quot;</span>, <span class="string">&quot;lazy&quot;</span>);</span><br><span class="line">channel.queueDeclare(<span class="string">&quot;myqueue&quot;</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, args);</span><br></pre></td></tr></table></figure>



<h2 id="11-主定位器"><a href="#11-主定位器" class="headerlink" title="11.主定位器"></a>11.主定位器</h2><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201281553788.png" alt="image-20220128155338286"></p>
<h1 id="九、延时队列"><a href="#九、延时队列" class="headerlink" title="九、延时队列"></a>九、延时队列</h1><p>延时队列是死信队列的一种</p>
<h2 id="9-1-延迟队列概念"><a href="#9-1-延迟队列概念" class="headerlink" title="9.1.延迟队列概念"></a>9.1.延迟队列概念</h2><p>延时队列,队列内部是有序的，最重要的特性就体现在它的延时属性上，延时队列中的元素是希望在指定时间到了以后或之前取出和处理，简单来说，延时队列就是用来存放需要在指定时间被处理的元素的队列。</p>
<h2 id="9-2-延迟队列使用场景"><a href="#9-2-延迟队列使用场景" class="headerlink" title="9.2. 延迟队列使用场景"></a>9.2. 延迟队列使用场景</h2><ol>
<li>订单在十分钟之内未支付则自动取消</li>
<li>新创建的店铺，如果在十天内都没有上传过商品，则自动发送消息提醒。</li>
<li>用户注册成功后，如果三天内没有登陆则进行短信提醒。</li>
<li>用户发起退款，如果三天内没有得到处理则通知相关运营人员。</li>
<li>预定会议后，需要在预定的时间点前十分钟通知各个与会人员参加会议</li>
</ol>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201290106447.png" alt="image-20220129010646320"></p>
<h2 id="9-3-整合SpringBoot"><a href="#9-3-整合SpringBoot" class="headerlink" title="9.3.整合SpringBoot"></a>9.3.整合SpringBoot</h2><h3 id="1-创建springboot工程"><a href="#1-创建springboot工程" class="headerlink" title="1.创建springboot工程"></a>1.创建springboot工程</h3><h3 id="2-导入依赖"><a href="#2-导入依赖" class="headerlink" title="2.导入依赖"></a>2.导入依赖</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!--RabbitMQ 依赖--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-amqp<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>fastjson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.47<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--swagger--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.springfox<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>springfox-swagger2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.9.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.springfox<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>springfox-swagger-ui<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.9.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--RabbitMQ 测试依赖--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.amqp<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-rabbit-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h3 id="3-修改配置文件"><a href="#3-修改配置文件" class="headerlink" title="3.修改配置文件"></a>3.修改配置文件</h3><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">spring.rabbitmq.host</span>=<span class="string">192.168.227.50  #虚拟机ip</span></span><br><span class="line"><span class="meta">spring.rabbitmq.port</span>=<span class="string">5672   #rabbitmq的端口</span></span><br><span class="line"><span class="meta">spring.rabbitmq.username</span>=<span class="string">admin  </span></span><br><span class="line"><span class="meta">spring.rabbbitmq.password</span>=<span class="string">123</span></span><br></pre></td></tr></table></figure>

<h3 id="4-添加Swagger配置类"><a href="#4-添加Swagger配置类" class="headerlink" title="4.添加Swagger配置类"></a>4.添加Swagger配置类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableSwagger2</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SwaggerConfig</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Docket <span class="title">webApiConfig</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Docket(DocumentationType.SWAGGER_2)</span><br><span class="line">                .groupName(<span class="string">&quot;webApi&quot;</span>)</span><br><span class="line">                .apiInfo(webApiInfo())</span><br><span class="line">                .select()</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">private</span> ApiInfo <span class="title">webApiInfo</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ApiInfoBuilder()</span><br><span class="line">                .title(<span class="string">&quot;rabbitmq 接口文档&quot;</span>)</span><br><span class="line">                .description(<span class="string">&quot;本文档描述了 rabbitmq 微服务接口定义&quot;</span>)</span><br><span class="line">                .version(<span class="string">&quot;1.0&quot;</span>)</span><br><span class="line">                .contact(<span class="keyword">new</span> Contact(<span class="string">&quot;yuan&quot;</span>, <span class="string">&quot;http://rmbyuan.io&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;1976622404@qq.com&quot;</span>))</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="9-4-RabbitMQ中的TTL"><a href="#9-4-RabbitMQ中的TTL" class="headerlink" title="9.4.RabbitMQ中的TTL"></a>9.4.RabbitMQ中的TTL</h2><p>TTL 是什么呢？TTL 是 RabbitMQ 中一个消息或者队列的属性，表明一条消息或者该队列中的所有消息的最大存活时间，<strong>单位是毫秒</strong>。换句话说，如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列，那么这条消息如果在 TTL 设置的时间内没有被消费，则会成为”死信”。如果同时配置了队列的 TTL 和消息的TTL，那么较小的那个值将会被使用，有两种方式设置 TTL。</p>
<h3 id="1-队列设置TTL"><a href="#1-队列设置TTL" class="headerlink" title="1.队列设置TTL"></a>1.队列设置TTL</h3><p>在创建队列时设置队列的”x-message-ttl”属性</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//声明队列的TTL</span></span><br><span class="line">args.put(<span class="string">&quot;x-message-ttl&quot;</span>,<span class="number">5000</span>);</span><br><span class="line"><span class="keyword">return</span> QueueBuilder.durable().withArguments(args).build();</span><br></pre></td></tr></table></figure>

<h3 id="2-消息设置TTL"><a href="#2-消息设置TTL" class="headerlink" title="2.消息设置TTL"></a>2.消息设置TTL</h3><p>针对每条消息设置TTL</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">rabbitTemplate.convertAndSend(exchange,routingKey,message,correlationData -&gt; &#123;</span><br><span class="line">	correlationData.getMessageProperties().setExpiration(ttlTime);</span><br><span class="line">	<span class="keyword">return</span> correlationData;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="3-两者的区别"><a href="#3-两者的区别" class="headerlink" title="3.两者的区别"></a>3.两者的区别</h3><p>如果设置了队列的 TTL 属性，那么一旦消息过期，就会被队列丢弃(如果配置了死信队列被丢到死信队列中)，而第二种方式，消息即使过期，也不一定会被马上丢弃，因为<strong>消息是否过期是在即将投递到消费者之前判定的</strong>如果当前队列有严重的消息积压情况，则已过期的消息也许还能存活较长时间；另外，还需要注意的一点是，如果不设置 TTL，表示消息永远不会过期，如果将 TTL 设置为 0，则表示除非此时可以直接投递该消息到消费者，否则该消息将会被丢弃。</p>
<h2 id="9-5-队列TTL"><a href="#9-5-队列TTL" class="headerlink" title="9.5.队列TTL"></a>9.5.队列TTL</h2><h3 id="1-代码架构图"><a href="#1-代码架构图" class="headerlink" title="1.代码架构图"></a>1.代码架构图</h3><p>创建两个队列 QA 和 QB，两者队列 TTL 分别设置为 10S 和 40S，然后在创建一个交换机 X 和死信交换机 Y，它们的类型都是 direct，创建一个死信队列 QD，它们的绑定关系如下：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201310142761.png" alt="image-20220131014013860"></p>
<h3 id="2-配置文件类代码"><a href="#2-配置文件类代码" class="headerlink" title="2.配置文件类代码"></a>2.配置文件类代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TtlQueueConfig</span> </span>&#123;</span><br><span class="line">    <span class="comment">//普通交换机</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String X_EXCHANGE = <span class="string">&quot;X&quot;</span>;</span><br><span class="line">    <span class="comment">//死信交换机</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String Y_DEAD_LETTER_EXCHANGE = <span class="string">&quot;Y&quot;</span>;</span><br><span class="line">    <span class="comment">//普通队列QA和QB</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_A = <span class="string">&quot;QA&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_B = <span class="string">&quot;QB&quot;</span>;</span><br><span class="line">    <span class="comment">//死信队列QD</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DEAD_LETTER_QUEUE = <span class="string">&quot;QD&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 声明 xExchange</span></span><br><span class="line">    <span class="meta">@Bean(&quot;xExchange&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DirectExchange <span class="title">xExchange</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> DirectExchange(X_EXCHANGE);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 声明 xExchange</span></span><br><span class="line">    <span class="meta">@Bean(&quot;yExchange&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DirectExchange <span class="title">yExchange</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> DirectExchange(Y_DEAD_LETTER_EXCHANGE);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//声明队列 A ttl 为 10s 并绑定到对应的死信交换机</span></span><br><span class="line">    <span class="meta">@Bean(&quot;queueA&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">queueA</span><span class="params">()</span></span>&#123;</span><br><span class="line">        Map&lt;String, Object&gt; args = <span class="keyword">new</span> HashMap&lt;&gt;(<span class="number">3</span>);</span><br><span class="line">        <span class="comment">//声明当前队列绑定的死信交换机</span></span><br><span class="line">        args.put(<span class="string">&quot;x-dead-letter-exchange&quot;</span>, Y_DEAD_LETTER_EXCHANGE);</span><br><span class="line">        <span class="comment">//声明当前队列的死信路由 key</span></span><br><span class="line">        args.put(<span class="string">&quot;x-dead-letter-routing-key&quot;</span>, <span class="string">&quot;YD&quot;</span>);</span><br><span class="line">        <span class="comment">//声明队列的 TTL</span></span><br><span class="line">        args.put(<span class="string">&quot;x-message-ttl&quot;</span>, <span class="number">10000</span>);</span><br><span class="line">        <span class="keyword">return</span> QueueBuilder.durable(QUEUE_A).withArguments(args).build();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 声明队列 A 绑定 X 交换机</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">queueaBindingX</span><span class="params">(<span class="meta">@Qualifier(&quot;queueA&quot;)</span> Queue queueA,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  <span class="meta">@Qualifier(&quot;xExchange&quot;)</span> DirectExchange xExchange)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(queueA).to(xExchange).with(<span class="string">&quot;XA&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//声明队列 B ttl 为 40s 并绑定到对应的死信交换机</span></span><br><span class="line">    <span class="meta">@Bean(&quot;queueB&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">queueB</span><span class="params">()</span></span>&#123;</span><br><span class="line">        Map&lt;String, Object&gt; args = <span class="keyword">new</span> HashMap&lt;&gt;(<span class="number">3</span>);</span><br><span class="line">        <span class="comment">//声明当前队列绑定的死信交换机</span></span><br><span class="line">        args.put(<span class="string">&quot;x-dead-letter-exchange&quot;</span>, Y_DEAD_LETTER_EXCHANGE);</span><br><span class="line">        <span class="comment">//声明当前队列的死信路由 key</span></span><br><span class="line">        args.put(<span class="string">&quot;x-dead-letter-routing-key&quot;</span>, <span class="string">&quot;YD&quot;</span>);</span><br><span class="line">        <span class="comment">//声明队列的 TTL</span></span><br><span class="line">        args.put(<span class="string">&quot;x-message-ttl&quot;</span>, <span class="number">30000</span>);</span><br><span class="line">        <span class="keyword">return</span> QueueBuilder.durable(QUEUE_B).withArguments(args).build();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//声明队列 B 绑定 X 交换机</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">queuebBindingX</span><span class="params">(<span class="meta">@Qualifier(&quot;queueB&quot;)</span> Queue queue1B,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  <span class="meta">@Qualifier(&quot;xExchange&quot;)</span> DirectExchange xExchange)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(queue1B).to(xExchange).with(<span class="string">&quot;XB&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//声明死信队列 QD</span></span><br><span class="line">    <span class="meta">@Bean(&quot;queueD&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">queueD</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> QueueBuilder.durable(DEAD_LETTER_QUEUE).build();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//声明死信队列 QD 绑定关系</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">deadLetterBindingQAD</span><span class="params">(<span class="meta">@Qualifier(&quot;queueD&quot;)</span> Queue queueD,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        <span class="meta">@Qualifier(&quot;yExchange&quot;)</span> DirectExchange yExchange)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(queueD).to(yExchange).with(<span class="string">&quot;YD&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-消息生产者代码"><a href="#3-消息生产者代码" class="headerlink" title="3.消息生产者代码"></a>3.消息生产者代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;ttl&quot;)</span></span><br><span class="line"><span class="meta">@RestController</span>  <span class="comment">//不走试图解析器</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SendMsgController</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RabbitTemplate rabbitTemplate;</span><br><span class="line">    <span class="comment">//Restful风格</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;sendMsg/&#123;message&#125;&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendMsg</span><span class="params">(<span class="meta">@PathVariable</span> String message)</span></span>&#123;</span><br><span class="line">        log.info(<span class="string">&quot;当前时间:&#123;&#125;,发送一条消息给两个ttl队列:&#123;&#125;&quot;</span>,<span class="keyword">new</span> Date(),message);</span><br><span class="line">        <span class="comment">//转换并发送</span></span><br><span class="line">        rabbitTemplate.convertAndSend(<span class="string">&quot;X&quot;</span>, <span class="string">&quot;XA&quot;</span>, <span class="string">&quot;消息来自 ttl 为 10S 的队列: &quot;</span>+message);</span><br><span class="line">        rabbitTemplate.convertAndSend(<span class="string">&quot;X&quot;</span>, <span class="string">&quot;XB&quot;</span>, <span class="string">&quot;消息来自 ttl 为 30S 的队列: &quot;</span>+message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="4-消息消费者代码"><a href="#4-消息消费者代码" class="headerlink" title="4.消息消费者代码"></a>4.消息消费者代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeadLetterQueueConsumer</span> </span>&#123;</span><br><span class="line">    <span class="meta">@RabbitListener(queues = &quot;QD&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">receiveD</span><span class="params">(Message message, Channel channel)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        String msg = <span class="keyword">new</span> String(message.getBody());</span><br><span class="line">        log.info(<span class="string">&quot;当前时间：&#123;&#125;,收到死信队列信息&#123;&#125;&quot;</span>, <span class="keyword">new</span> Date().toString(), msg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后在浏览器中发送一个请求<a target="_blank" rel="noopener" href="http://localhost:8080/ttl/sendMsg/%E5%93%88%E5%93%88">http://localhost:8080/ttl/sendMsg/哈哈</a></p>
<p>之后在控制台我们会看到以下状态：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201310158036.png" alt="image-20220131015808865"></p>
<p>第一条消息在 10S 后变成了死信消息，然后被消费者消费掉，第二条消息在 40S 之后变成了死信消息，然后被消费掉，这样一个延时队列就打造完成了。</p>
<p>但以上述这种方式存在一个<strong>缺点</strong>，就是<strong>每增加一个新的时间需求，就要新增一个队列</strong></p>
<h2 id="9-6-延时队列优化"><a href="#9-6-延时队列优化" class="headerlink" title="9.6.延时队列优化"></a>9.6.延时队列优化</h2><h3 id="1-代码架构图-1"><a href="#1-代码架构图-1" class="headerlink" title="1.代码架构图"></a>1.代码架构图</h3><p>在前者的架构图基础上，新增了一个队列 QC,绑定关系如下,该队列不设置 TTL 时间</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201310201595.png" alt="image-20220131020121753"></p>
<h3 id="2-配置文件类代码-1"><a href="#2-配置文件类代码-1" class="headerlink" title="2.配置文件类代码"></a>2.配置文件类代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MsgTtlQueueConfig</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String Y_DEAD_LETTER_EXCHANGE = <span class="string">&quot;Y&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_C = <span class="string">&quot;QC&quot;</span>;</span><br><span class="line">    <span class="comment">//声明队列 C 死信交换机</span></span><br><span class="line">    <span class="meta">@Bean(&quot;queueC&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">queueB</span><span class="params">()</span></span>&#123;</span><br><span class="line">        Map&lt;String, Object&gt; args = <span class="keyword">new</span> HashMap&lt;&gt;(<span class="number">3</span>);</span><br><span class="line">        <span class="comment">//声明当前队列绑定的死信交换机</span></span><br><span class="line">        args.put(<span class="string">&quot;x-dead-letter-exchange&quot;</span>, Y_DEAD_LETTER_EXCHANGE);</span><br><span class="line">        <span class="comment">//声明当前队列的死信路由 key</span></span><br><span class="line">        args.put(<span class="string">&quot;x-dead-letter-routing-key&quot;</span>, <span class="string">&quot;YD&quot;</span>);</span><br><span class="line">        <span class="comment">//没有声明 TTL 属性</span></span><br><span class="line">        <span class="keyword">return</span> QueueBuilder.durable(QUEUE_C).withArguments(args).build();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//声明队列 B 绑定 X 交换机</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">queuecBindingX</span><span class="params">(<span class="meta">@Qualifier(&quot;queueC&quot;)</span> Queue queueC,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  <span class="meta">@Qualifier(&quot;xExchange&quot;)</span> DirectExchange xExchange)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(queueC).to(xExchange).with(<span class="string">&quot;XC&quot;</span>);</span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-消息生产者代码-1"><a href="#3-消息生产者代码-1" class="headerlink" title="3.消息生产者代码"></a>3.消息生产者代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;sendExpirationMsg/&#123;message&#125;/&#123;ttlTime&#125;&quot;)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendMsg</span><span class="params">(<span class="meta">@PathVariable</span> String message,<span class="meta">@PathVariable</span> String ttlTime)</span> </span>&#123;</span><br><span class="line">    rabbitTemplate.convertAndSend(<span class="string">&quot;X&quot;</span>, <span class="string">&quot;XC&quot;</span>, message, correlationData -&gt;&#123;</span><br><span class="line">        correlationData.getMessageProperties().setExpiration(ttlTime);</span><br><span class="line">        <span class="keyword">return</span> correlationData;</span><br><span class="line">    &#125;);</span><br><span class="line">    log.info(<span class="string">&quot;当前时间：&#123;&#125;,发送一条时长&#123;&#125;毫秒 TTL 信息给队列 C:&#123;&#125;&quot;</span>, <span class="keyword">new</span> Date(),ttlTime, message);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以通过Restful风格，将ttl作为一个参数，来设置ttl过期时间</p>
<p><strong>发起请求</strong></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">http://localhost:8080/ttl/sendExpirationMsg/你好1/20000</span><br><span class="line">http://localhost:8080/ttl/sendExpirationMsg/你好2/2000</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201310216583.png" alt="image-20220131021621746"></p>
<p>看起来似乎没什么问题，但是在最开始的时候，就介绍过如果使用在消息属性上设置 TTL 的方式，消息可能并不会按时“死亡“，因为 <strong>RabbitMQ</strong> <strong>只会检查第一个消息是否过期</strong>，如果过期则丢到死信队列，<strong>如果第一个消息的延时时长很长，而第二个消息的延时时长很短，第二个消息并不会优先得到执行</strong>。</p>
<p>也就是说，消息进入死信队列的时间并不会因为第二次发送消息延时时长短而优先执行。</p>
<h2 id="9-7-RabbitMQ插件实现延迟队列"><a href="#9-7-RabbitMQ插件实现延迟队列" class="headerlink" title="9.7.RabbitMQ插件实现延迟队列"></a>9.7.RabbitMQ插件实现延迟队列</h2><p>上述提到的问题，如果不能实现在消息粒度上的 TTL，并使其在设置的 TTL 时间及时死亡，就无法设计成一个通用的延时队列。而我们可以利用延时队列插件解决这个问题</p>
<h3 id="1-安装延时队列插件"><a href="#1-安装延时队列插件" class="headerlink" title="1.安装延时队列插件"></a>1.安装延时队列插件</h3><p>在官网上下载 <a target="_blank" rel="noopener" href="https://www.rabbitmq.com/community-plugins.html%EF%BC%8C%E4%B8%8B%E8%BD%BD**rabbitmq_delayed_message_exchange">https://www.rabbitmq.com/community-plugins.html，下载**rabbitmq_delayed_message_exchange</a>** 插件，然后解压放置到 RabbitMQ 的插件目录,也就是/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins。我们需要注意的是<strong>下载的文件后缀是.ez</strong></p>
<p>进入 RabbitMQ 的安装目录下的 plugins 目录，执行下面命令让该插件生效，然后重启 RabbitMQ</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins</span><br><span class="line">rabbitmq-plugins <span class="built_in">enable</span> rabbitmq_delayed_message_exchange</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201312016191.png"></p>
<p>这里需要注意的是，rabbitmq_delayed_message_exchange的版本，如果rabbitmq使用的是3.9.12的，插件需要使用3.9的，否则会出现以下情况：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201312018722.png" alt="image-20220131201844046"></p>
<p>在没安装插件之前，通过UI界面添加交换机类型有以下选项：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201312013673.png" alt="image-20220131201331440"></p>
<p>安装延迟插件之后，会多出一种类型，如图所示：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201312031316.png" alt="image-20220131203134583"></p>
<h3 id="2-代码架构图"><a href="#2-代码架构图" class="headerlink" title="2.代码架构图"></a>2.代码架构图</h3><p>在这里新增了一个队列 delayed.queue,一个自定义交换机 delayed.exchange，绑定关系如下:</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202201312259937.png" alt="image-20220131225919024"></p>
<h3 id="3-配置文件类代码"><a href="#3-配置文件类代码" class="headerlink" title="3.配置文件类代码"></a>3.配置文件类代码</h3><p>安装完插件之后我们可以使用新的类型，该类型的消息支持延迟投递机制，消息传递后并不会立即投递到目标队列中，而是存储在 mnesia(一个分布式数据系统)表中，当达到投递时间时，才投递到目标队列中。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DelayedQueueConfig</span> </span>&#123;</span><br><span class="line">    <span class="comment">//定义延时交换机</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DELAYED_EXCHANGE_NAME = <span class="string">&quot;delayed.exchange&quot;</span>;</span><br><span class="line">    <span class="comment">//定义延时队列</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DELAYED_QUEUE_NAME = <span class="string">&quot;delayed.queue&quot;</span>;</span><br><span class="line">    <span class="comment">//绑定队列与交换机的routingKey</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DELAYED_ROUTING_KEY = <span class="string">&quot;delayed.routingKey&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">delayedQueue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Queue(DELAYED_QUEUE_NAME);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//自定义交换机 我们在这里定义的是一个延迟交换机</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> CustomExchange <span class="title">delayedExchange</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Map&lt;String, Object&gt; args = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        <span class="comment">//自定义交换机延迟类型</span></span><br><span class="line">        args.put(<span class="string">&quot;x-delayed-type&quot;</span>, <span class="string">&quot;direct&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> CustomExchange(DELAYED_EXCHANGE_NAME, <span class="string">&quot;x-delayed-message&quot;</span>, <span class="keyword">true</span>, <span class="keyword">false</span>, args);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//绑定队列和交换机</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">bindingDelayedQueue</span><span class="params">(<span class="meta">@Qualifier(&quot;delayedQueue&quot;)</span> Queue delayedQueue,</span></span></span><br><span class="line"><span class="params"><span class="function">                                       <span class="meta">@Qualifier(&quot;delayedExchange&quot;)</span> CustomExchange delayedExchange)</span></span>&#123;</span><br><span class="line">        <span class="comment">//noargs()表示构建这个绑定</span></span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里我们使用的交换机是 CustomExchange，这是一个 Spring 中提供的交换机，创建 CustomExchange 时有五个参数，含义分别如下：</p>
<ul>
<li><p>交换机名称。</p>
</li>
<li><p>交换机类型，这个地方是固定的。</p>
</li>
<li><p>交换机是否持久化。</p>
</li>
<li><p>如果没有队列绑定到交换机，交换机是否删除。</p>
</li>
<li><p>其他参数args。</p>
</li>
</ul>
<p>最后一个 args 参数中，指定了交换机消息分发的类型，这个类型就是大家熟知的 direct、fanout、topic 以及 header 几种，用了哪种类型，将来交换机分发消息就按哪种方式来</p>
<h3 id="4-消息生产者代码"><a href="#4-消息生产者代码" class="headerlink" title="4.消息生产者代码"></a>4.消息生产者代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//延迟队列发送消息</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DELAYED_EXCHANGE_NAME = <span class="string">&quot;delayed.exchange&quot;</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DELAYED_ROUTING_KEY = <span class="string">&quot;delayed.routingKey&quot;</span>;</span><br><span class="line"><span class="meta">@GetMapping(&quot;sendDelayMsg/&#123;message&#125;/&#123;delayTime&#125;&quot;)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendMsg</span><span class="params">(<span class="meta">@PathVariable</span> String message,<span class="meta">@PathVariable</span> Integer delayTime)</span> </span>&#123;</span><br><span class="line">    rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, message,</span><br><span class="line">                                  correlationData -&gt;&#123;</span><br><span class="line">                                      correlationData.getMessageProperties().setDelay(delayTime);</span><br><span class="line">                                      <span class="keyword">return</span> correlationData;</span><br><span class="line">                                  &#125;);</span><br><span class="line">    log.info(<span class="string">&quot; 当 前 时 间 : &#123;&#125;, 发送一条延迟 &#123;&#125; 毫秒的信息给队列 delayed.queue:&#123;&#125;&quot;</span>, <span class="keyword">new</span></span><br><span class="line">             Date(),delayTime, message);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="5-消息消费者代码"><a href="#5-消息消费者代码" class="headerlink" title="5.消息消费者代码"></a>5.消息消费者代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DELAYED_QUEUE_NAME = <span class="string">&quot;delayed.queue&quot;</span>;</span><br><span class="line"><span class="meta">@RabbitListener(queues = DELAYED_QUEUE_NAME)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">receiveDelayedQueue</span><span class="params">(Message message)</span></span>&#123;</span><br><span class="line">    String msg = <span class="keyword">new</span> String(message.getBody());</span><br><span class="line">    log.info(<span class="string">&quot;当前时间:&#123;&#125;,收到延时队列的消息:&#123;&#125;&quot;</span>, <span class="keyword">new</span> Date().toString(), msg);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>发起请求：</strong></p>
<p><a target="_blank" rel="noopener" href="http://localhost:8080/ttl/sendDelayMsg/come">http://localhost:8080/ttl/sendDelayMsg/come</a> on baby1/20000</p>
<p><a target="_blank" rel="noopener" href="http://localhost:8080/ttl/sendDelayMsg/come">http://localhost:8080/ttl/sendDelayMsg/come</a> on baby2/2000</p>
<p>控制台输出：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202010052049.png" alt="image-20220201005240629"></p>
<h3 id="6-总结"><a href="#6-总结" class="headerlink" title="6.总结"></a>6.总结</h3><p>延时队列在需要延时处理的场景下非常有用，使用 RabbitMQ 来实现延时队列可以很好的利用RabbitMQ 的特性，如：消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。另外，通过 RabbitMQ 集群的特性，可以很好的解决单点故障问题，不会因为单个节点挂掉导致延时队列不可用或者消息丢失。当然，延时队列还有很多其它选择，比如利用 Java 的 DelayQueue，利用 Redis 的 zset，利用 Quartz或者利用 kafka 的时间轮，这些方式各有特点,看需要适用的场景</p>
<h1 id="十、发布确认高级"><a href="#十、发布确认高级" class="headerlink" title="十、发布确认高级"></a>十、发布确认高级</h1><p>参考官网学习：<a target="_blank" rel="noopener" href="https://www.rabbitmq.com/tutorials/tutorial-seven-java.html">https://www.rabbitmq.com/tutorials/tutorial-seven-java.html</a></p>
<p>在生产环境中由于一些不明原因，导致 rabbitmq 重启，在 RabbitMQ 重启期间生产者消息投递失败，导致消息丢失，需要手动处理和恢复。</p>
<blockquote>
<p>如何才能进行 RabbitMQ 的消息可靠投递呢？</p>
</blockquote>
<p>特别是在这样比较极端的情况，RabbitMQ 集群不可用的时候，无法投递的消息该如何处理呢</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">应 用 [xxx] 在[08-15 16:36:04]发生[ 错误日志异常 ]，alertId=[xxx]。 由[org.springframework.amqp.rabbit.listener.BlockingQueueConsumer:start:620] 触发。</span><br><span class="line"></span><br><span class="line">应用 xxx 可能原因如下</span><br><span class="line">服务名为： </span><br><span class="line">异常为： org.springframework.amqp.rabbit.listener.BlockingQueueConsumer:start:620, </span><br><span class="line">产 生 原 因 如 下 :1.org.springframework.amqp.rabbit.listener.QueuesNotAvailableException: </span><br><span class="line">Cannot prepare queue for listener. Either the queue doesn&#x27;t exist or the broker will not </span><br><span class="line">allow us to use it.||Consumer received fatal=false exception on startup:</span><br></pre></td></tr></table></figure>

<h2 id="10-1-发布确认springboot-版本"><a href="#10-1-发布确认springboot-版本" class="headerlink" title="10.1.发布确认springboot 版本"></a>10.1.发布确认springboot 版本</h2><h3 id="10-1-1-确认方案"><a href="#10-1-1-确认方案" class="headerlink" title="10.1.1.确认方案"></a>10.1.1.确认方案</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202060009550.png" alt="image-20220206000914381"></p>
<h3 id="10-1-2-实验目的"><a href="#10-1-2-实验目的" class="headerlink" title="10.1.2.实验目的"></a>10.1.2.实验目的</h3><p>当生产者发布消息时，交换机不管是否接收到消息都会回调。而当生产者发送两条消息，但是只有一条消息的routingKey是存在的，则消费者只能接收到这条消息，而另一条消息就会被丢弃。</p>
<h3 id="10-1-3-代码架构图"><a href="#10-1-3-代码架构图" class="headerlink" title="10.1.3.代码架构图"></a>10.1.3.代码架构图</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202060133422.png" alt="image-20220206013338753"></p>
<h3 id="10-1-4-配置文件"><a href="#10-1-4-配置文件" class="headerlink" title="10.1.4.配置文件"></a>10.1.4.配置文件</h3><p>在配置文件当中需要添加</p>
<p><strong>spring.rabbitmq.publisher-confirm-type=correlated</strong></p>
<ul>
<li><p>NONE：禁用发布确认模式，是默认值</p>
</li>
<li><p>CORRELATED：发布消息成功到交换器后会触发回调方法</p>
</li>
<li><p>SIMPLE：经测试有两种效果，</p>
<ul>
<li><p>其一效果和 CORRELATED 值一样会触发回调方法，</p>
</li>
<li><p>其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法等待 broker 节点返回发送结果，根据返回结果来判定下一步的逻辑，要注意的点是waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel，则接下来无法发送消息到 broker</p>
</li>
</ul>
</li>
</ul>
<h3 id="10-1-5-添加配置文件"><a href="#10-1-5-添加配置文件" class="headerlink" title="10.1.5.添加配置文件"></a>10.1.5.添加配置文件</h3><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">spring.rabbitmq.host</span>=<span class="string">192.168.227.50</span></span><br><span class="line"><span class="meta">spring.rabbitmq.port</span>=<span class="string">5672</span></span><br><span class="line"><span class="meta">spring.rabbitmq.username</span>=<span class="string">admin</span></span><br><span class="line"><span class="meta">spring.rabbitmq.password</span>=<span class="string">123</span></span><br><span class="line"><span class="meta">spring.rabbitmq.publisher-confirm-type</span>=<span class="string">correlated</span></span><br></pre></td></tr></table></figure>

<h3 id="10-1-6-配置类"><a href="#10-1-6-配置类" class="headerlink" title="10.1.6.配置类"></a>10.1.6.配置类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConfirmConfig</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String CONFIRM_EXCHANGE_NAME = <span class="string">&quot;confirm.exchange&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String CONFIRM_QUEUE_NAME = <span class="string">&quot;confirm.queue&quot;</span>;</span><br><span class="line">    <span class="comment">//声明交换机</span></span><br><span class="line">    <span class="meta">@Bean(&quot;confirmExchange&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DirectExchange <span class="title">directExchange</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> DirectExchange(CONFIRM_EXCHANGE_NAME,<span class="keyword">false</span>,<span class="keyword">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//声明队列</span></span><br><span class="line">    <span class="meta">@Bean(&quot;confirmQueue&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">confirmQueue</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Queue(CONFIRM_QUEUE_NAME);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//绑定队列和交换机</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">binding</span><span class="params">(<span class="meta">@Qualifier(&quot;confirmExchange&quot;)</span> DirectExchange directExchange,</span></span></span><br><span class="line"><span class="params"><span class="function">                           <span class="meta">@Qualifier(&quot;confirmQueue&quot;)</span> Queue queue)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(queue).to(directExchange).with(<span class="string">&quot;key1&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="10-1-7-回调接口"><a href="#10-1-7-回调接口" class="headerlink" title="10.1.7.回调接口"></a>10.1.7.回调接口</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyCallBack</span>  <span class="keyword">implements</span> <span class="title">RabbitTemplate</span>.<span class="title">ConfirmCallback</span></span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 交换机不管是否收到消息的一个回调方法</span></span><br><span class="line"><span class="comment">     * CorrelationData</span></span><br><span class="line"><span class="comment">     * 消息相关数据</span></span><br><span class="line"><span class="comment">     * ack</span></span><br><span class="line"><span class="comment">     * 交换机是否收到消息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">confirm</span><span class="params">(CorrelationData correlationData, <span class="keyword">boolean</span> ack, String cause)</span> </span>&#123;</span><br><span class="line">        String id = correlationData != <span class="keyword">null</span> ? correlationData.getId() : <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">if</span>(ack)&#123;</span><br><span class="line">            log.info(<span class="string">&quot;交换机已经收到 id 为:&#123;&#125;的消息&quot;</span>,id);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            log.info(<span class="string">&quot;交换机还未收到 id 为:&#123;&#125;消息,由于原因:&#123;&#125;&quot;</span>,id,cause);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="10-1-8-消息生产者"><a href="#10-1-8-消息生产者" class="headerlink" title="10.1.8.消息生产者"></a>10.1.8.消息生产者</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/confirm&quot;)</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Producer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String CONFIRM_EXCHANGE_NAME = <span class="string">&quot;confirm.exchange&quot;</span>;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RabbitTemplate rabbitTemplate;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MyCallBack myCallBack;</span><br><span class="line">    <span class="comment">//依赖注入rabbitTemplate之后再设置它的回调对象</span></span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">        rabbitTemplate.setConfirmCallback(myCallBack);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@GetMapping(&quot;sendMessage/&#123;message&#125;&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendMessage</span><span class="params">(<span class="meta">@PathVariable</span> String message)</span></span>&#123;</span><br><span class="line">        <span class="comment">//指定消息 id 为 1</span></span><br><span class="line">        CorrelationData correlationData1=<span class="keyword">new</span> CorrelationData(<span class="string">&quot;1&quot;</span>);</span><br><span class="line">        String routingKey=<span class="string">&quot;key1&quot;</span>;</span><br><span class="line">        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME,routingKey,</span><br><span class="line">                message+routingKey,correlationData1);</span><br><span class="line">        log.info(<span class="string">&quot;发送消息内容:&#123;&#125;&quot;</span>,message+<span class="string">&quot;,id=1&quot;</span>);</span><br><span class="line">        CorrelationData correlationData2=<span class="keyword">new</span> CorrelationData(<span class="string">&quot;2&quot;</span>);</span><br><span class="line">        routingKey=<span class="string">&quot;key2&quot;</span>;</span><br><span class="line">        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME,</span><br><span class="line">                routingKey,message+routingKey,correlationData2);</span><br><span class="line">        log.info(<span class="string">&quot;发送消息内容:&#123;&#125;&quot;</span>,message+<span class="string">&quot;,id=2&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="10-1-9-消息消费者"><a href="#10-1-9-消息消费者" class="headerlink" title="10.1.9.消息消费者"></a>10.1.9.消息消费者</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConfirmConsumer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String CONFIRM_QUEUE_NAME = <span class="string">&quot;confirm.queue&quot;</span>;</span><br><span class="line">    <span class="meta">@RabbitListener(queues =CONFIRM_QUEUE_NAME)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">receiveMsg</span><span class="params">(Message message)</span></span>&#123;</span><br><span class="line">        String msg=<span class="keyword">new</span> String(message.getBody());</span><br><span class="line">        log.info(<span class="string">&quot;接受到队列 confirm.queue 消息:&#123;&#125;&quot;</span>,msg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>此时运行主启动类，然后在浏览器地址栏中输入：<a target="_blank" rel="noopener" href="http://localhost:8080/confirm/sendMessage/%E6%9C%9D%E8%BE%9E%E7%99%BD%E5%B8%9D%E5%BD%A9%E4%BA%91%E9%97%B4">http://localhost:8080/confirm/sendMessage/朝辞白帝彩云间</a></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202060141971.png" alt="image-20220206014112893"></p>
<p>虽然交换机接收到两条消息，但是由于第二条消息是发送到绑定关系为key2的队列中，因此会导致消息被丢弃。</p>
<h3 id="知识点补充"><a href="#知识点补充" class="headerlink" title="知识点补充"></a>知识点补充</h3><blockquote>
<p>@PostConstruct基本</p>
</blockquote>
<p>@PostConstruct注解好多人以为是Spring提供的。其实是Java自己的注解。</p>
<p>Java中该注解的说明：@PostConstruct该注解被用来<strong>修饰一个非静态的void（）方法</strong>。被@PostConstruct修饰的方法<strong>会在服务器加载Servlet的时候运行</strong>，并且<strong>只会被服务器执行一次</strong>。PostConstruct在构造函数之后执行，init（）方法之前执行。</p>
<p>通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序：Constructor(构造方法) -&gt; @Autowired(依赖注入) -&gt; @PostConstruct(注释的方法)</p>
<h2 id="10-2-回退消息"><a href="#10-2-回退消息" class="headerlink" title="10.2.回退消息"></a>10.2.回退消息</h2><p>上述是以前学过的内容也是本章节主题的基础，从这里才是本章节的主题。</p>
<h3 id="10-2-1-Mandatory参数"><a href="#10-2-1-Mandatory参数" class="headerlink" title="10.2.1.Mandatory参数"></a>10.2.1.Mandatory参数</h3><p><strong>在仅开启了生产者确认机制的情况下，交换机接收到消息后，会直接给消息生产者发送确认消息，如果发现该消息不可路由，那么消息会被直接丢弃，此时生产者是不知道消息被丢弃这个事件的</strong>。那么如何让无法被路由的消息被处理呢？我们可以通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者</p>
<h3 id="10-2-2-消息生产者代码"><a href="#10-2-2-消息生产者代码" class="headerlink" title="10.2.2.消息生产者代码"></a>10.2.2.消息生产者代码</h3><p>需要添加的代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"> <span class="meta">@PostConstruct</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">    rabbitTemplate.setConfirmCallback(myCallBack);</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * true：</span></span><br><span class="line"><span class="comment">         * 交换机无法将消息进行路由时，会将该消息返回给生产者</span></span><br><span class="line"><span class="comment">         * false：</span></span><br><span class="line"><span class="comment">         * 如果发现消息无法进行路由，则直接丢弃</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">    rabbitTemplate.setMandatory(<span class="keyword">true</span>);</span><br><span class="line">    <span class="comment">//设置回退消息交给谁处理</span></span><br><span class="line">    rabbitTemplate.setReturnCallback(myCallBack);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="10-2-3-回调接口"><a href="#10-2-3-回调接口" class="headerlink" title="10.2.3.回调接口"></a>10.2.3.回调接口</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyCallBack</span>  <span class="keyword">implements</span> <span class="title">RabbitTemplate</span>.<span class="title">ConfirmCallback</span>,<span class="title">RabbitTemplate</span>.<span class="title">ReturnCallback</span> </span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 交换机不管是否收到消息的一个回调方法</span></span><br><span class="line"><span class="comment">     * CorrelationData</span></span><br><span class="line"><span class="comment">     * 消息相关数据</span></span><br><span class="line"><span class="comment">     * ack</span></span><br><span class="line"><span class="comment">     * 交换机是否收到消息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">confirm</span><span class="params">(CorrelationData correlationData, <span class="keyword">boolean</span> ack, String cause)</span> </span>&#123;</span><br><span class="line">        String id = correlationData != <span class="keyword">null</span> ? correlationData.getId() : <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">if</span>(ack)&#123;</span><br><span class="line">            log.info(<span class="string">&quot;交换机已经收到 id 为:&#123;&#125;的消息&quot;</span>,id);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            log.info(<span class="string">&quot;交换机还未收到 id 为:&#123;&#125;消息,由于原因:&#123;&#125;&quot;</span>,id,cause);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//回退消息</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">returnedMessage</span><span class="params">(Message message, <span class="keyword">int</span> replyCode,</span></span></span><br><span class="line"><span class="params"><span class="function">                                String replyText, String exchange,</span></span></span><br><span class="line"><span class="params"><span class="function">                                String routingKey)</span> </span>&#123;</span><br><span class="line">        log.info(<span class="string">&quot;消息:&#123;&#125;被服务器退回，退回原因:&#123;&#125;,交换机是:&#123;&#125;, 路由 key:&#123;&#125;&quot;</span>,<span class="keyword">new</span> String(message.getBody()),replyText,exchange,routingKey);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="10-2-4-配置文件"><a href="#10-2-4-配置文件" class="headerlink" title="10.2.4.配置文件"></a>10.2.4.配置文件</h3><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">spring.rabbitmq.host</span>=<span class="string">192.168.227.50</span></span><br><span class="line"><span class="meta">spring.rabbitmq.port</span>=<span class="string">5672</span></span><br><span class="line"><span class="meta">spring.rabbitmq.username</span>=<span class="string">admin</span></span><br><span class="line"><span class="meta">spring.rabbitmq.password</span>=<span class="string">123</span></span><br><span class="line"><span class="meta">spring.rabbitmq.publisher-confirm-type</span>=<span class="string">correlated</span></span><br><span class="line"><span class="comment">#开启队列回退</span></span><br><span class="line"><span class="meta">spring.rabbitmq.publisher-returns</span>=<span class="string">true</span></span><br></pre></td></tr></table></figure>

<p>此时，我们重新运行启动类，然后在浏览器地址栏中输入：<a target="_blank" rel="noopener" href="http://localhost:8080/confirm/sendMessage/%E4%BD%A0%E5%A5%BD">http://localhost:8080/confirm/sendMessage/你好</a></p>
<p>在控制台中会打印出以下信息：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202071629338.png" alt="image-20220207153651173"></p>
<h2 id="10-3-备份交换机"><a href="#10-3-备份交换机" class="headerlink" title="10.3.备份交换机"></a>10.3.备份交换机</h2><blockquote>
<p>设置备份交换机</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)</span><br><span class="line">                <span class="comment">//设置该交换机的备份交换机</span></span><br><span class="line">                .withArgument(<span class="string">&quot;alternate-exchange&quot;</span>,BACKUP_EXCHANGE_NAME);</span><br></pre></td></tr></table></figure>

<p>有了 mandatory 参数和回退消息，我们获得了对无法投递消息的感知能力，有机会在生产者的消息无法被投递时发现并处理。但有时候，我们并不知道该如何处理这些无法路由的消息，最多打个日志，然后触发报警，再来手动处理。而通过日志来处理这些无法路由的消息是很不优雅的做法，特别是当生产者所在的服务有多台机器的时候，手动复制日志会更加麻烦而且容易出错。而且设置 mandatory 参数会增加生产者的复杂性，需要添加处理这些被退回的消息的逻辑。如果既不想丢失消息，又不想增加生产者的复杂性，该怎么做呢？前面在设置死信队列的文章中，我们提到，可以为队列设置死信交换机来存储那些处理失败的消息，可是这些不可路由消息根本没有机会进入到队列，因此无法使用死信队列来保存消息。在 RabbitMQ 中，有一种备份交换机的机制存在，可以很好的应对这个问题。什么是备份交换机呢？备份交换机可以理解为 RabbitMQ 中交换机的“备胎”，当我们为某一个交换机声明一个对应的备份交换机时，就是为它创建一个备胎，<strong>当交换机接收到一条不可路由消息时，将会把这条消息转发到备份交换机中</strong>，由备份交换机来进行转发和处理，通常<strong>备份交换机的类型为 Fanout</strong> ，这样就能把所有消息都投递到与其绑定的队列中，然后我们在备份交换机下绑定一个队列，这样所有那些原交换机无法被路由的消息，就会都进入这个队列了。当然，我们还可以<strong>建立一个报警队列，用独立的消费者来进行监测和报警</strong></p>
<h3 id="10-3-1-代码架构图"><a href="#10-3-1-代码架构图" class="headerlink" title="10.3.1.代码架构图"></a>10.3.1.代码架构图</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202071700620.png" alt="image-20220207170039703"></p>
<h3 id="10-3-2-修改配置类"><a href="#10-3-2-修改配置类" class="headerlink" title="10.3.2.修改配置类"></a>10.3.2.修改配置类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConfirmConfig</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String CONFIRM_EXCHANGE_NAME = <span class="string">&quot;confirm.exchange&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String CONFIRM_QUEUE_NAME = <span class="string">&quot;confirm.queue&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String BACKUP_EXCHANGE_NAME = <span class="string">&quot;backup.exchange&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String BACKUP_QUEUE_NAME = <span class="string">&quot;backup.queue&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String WARNING_QUEUE_NAME = <span class="string">&quot;warning.queue&quot;</span>;</span><br><span class="line">    <span class="comment">//声明交换机</span></span><br><span class="line">    <span class="meta">@Bean(&quot;confirmExchange&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DirectExchange <span class="title">directExchange</span><span class="params">()</span></span>&#123;</span><br><span class="line">        ExchangeBuilder exchangeBuilder = ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)</span><br><span class="line">                <span class="comment">//设置该交换机的备份交换机</span></span><br><span class="line">                .withArgument(<span class="string">&quot;alternate-exchange&quot;</span>,BACKUP_EXCHANGE_NAME);</span><br><span class="line">        <span class="keyword">return</span> (DirectExchange) exchangeBuilder.build();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean(&quot;backupExchange&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> FanoutExchange <span class="title">backupExchange</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> FanoutExchange(BACKUP_EXCHANGE_NAME);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//声明队列</span></span><br><span class="line">    <span class="meta">@Bean(&quot;confirmQueue&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">confirmQueue</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Queue(CONFIRM_QUEUE_NAME);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean(&quot;warningQueue&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">warningQueue</span><span class="params">()</span></span>&#123; <span class="keyword">return</span> <span class="keyword">new</span> Queue(WARNING_QUEUE_NAME); &#125;</span><br><span class="line">    <span class="meta">@Bean(&quot;backupQueue&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Queue <span class="title">backupQueue</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Queue(BACKUP_QUEUE_NAME);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//绑定队列和交换机</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">binding</span><span class="params">(<span class="meta">@Qualifier(&quot;confirmExchange&quot;)</span> DirectExchange directExchange,</span></span></span><br><span class="line"><span class="params"><span class="function">                           <span class="meta">@Qualifier(&quot;confirmQueue&quot;)</span> Queue queue)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(queue).to(directExchange).with(<span class="string">&quot;key1&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">bindingBackUpQueue</span><span class="params">(<span class="meta">@Qualifier(&quot;backupQueue&quot;)</span> Queue backupQueue,</span></span></span><br><span class="line"><span class="params"><span class="function">                                      <span class="meta">@Qualifier(&quot;backupExchange&quot;)</span> FanoutExchange fanoutExchange)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(backupQueue).to(fanoutExchange);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Binding <span class="title">bindingWarningQueue</span><span class="params">(<span class="meta">@Qualifier(&quot;warningQueue&quot;)</span> Queue warningQueue,</span></span></span><br><span class="line"><span class="params"><span class="function">                                      <span class="meta">@Qualifier(&quot;backupExchange&quot;)</span> FanoutExchange fanoutExchange)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(warningQueue).to(fanoutExchange);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="10-3-3-消息消费者代码"><a href="#10-3-3-消息消费者代码" class="headerlink" title="10.3.3.消息消费者代码"></a>10.3.3.消息消费者代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WarningConsumer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String WARNING_QUEUE_NAME = <span class="string">&quot;warning.queue&quot;</span>;</span><br><span class="line">    <span class="meta">@RabbitListener(queues = WARNING_QUEUE_NAME)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">receiveWarningMsg</span><span class="params">(Message message)</span></span>&#123;</span><br><span class="line">        String msg = <span class="keyword">new</span> String(message.getBody());</span><br><span class="line">        log.error(<span class="string">&quot;报警发现不可路由消息:&#123;&#125;&quot;</span>,msg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="10-3-4-测试注意事项"><a href="#10-3-4-测试注意事项" class="headerlink" title="10.3.4.测试注意事项"></a>10.3.4.测试注意事项</h3><p><strong><font color='red'>重新启动项目的时候需要把原来的 confirm.exchange 删除因为我们修改了其绑定属性，不然报以下错：</font>font&gt;</strong></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202072244737.png" alt="image-20220207224457444"></p>
<h3 id="10-3-5-结果分析"><a href="#10-3-5-结果分析" class="headerlink" title="10.3.5.结果分析"></a>10.3.5.结果分析</h3><p>在浏览器地址栏中输入：<a target="_blank" rel="noopener" href="http://localhost:8080/confirm/sendMessage/%E4%BD%A0%E5%A5%BD">http://localhost:8080/confirm/sendMessage/你好</a></p>
<p>然后观察控制台：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202071845361.png" alt="image-20220207184537773"></p>
<p>mandatory 参数与备份交换机可以一起使用的时候，如果两者同时开启，消息究竟何去何从？谁优先级高，经过上面结果显示答案是<strong>备份交换机优先级高</strong>。<font color='red'>原因是消息被回退后，消息会被发送到备份交换机中。</font></p>
<h1 id="十一、RabbitMQ其他知识点"><a href="#十一、RabbitMQ其他知识点" class="headerlink" title="十一、RabbitMQ其他知识点"></a>十一、RabbitMQ其他知识点</h1><h2 id="11-1-幂等性"><a href="#11-1-幂等性" class="headerlink" title="11.1.幂等性"></a>11.1.幂等性</h2><h3 id="11-1-1-概念"><a href="#11-1-1-概念" class="headerlink" title="11.1.1.概念"></a>11.1.1.概念</h3><p>用户对于同一操作发起的一次请求或者多次请求的结果是一致的，不会因为多次点击而产生了副作用。举个最简单的例子，那就是支付，用户购买商品后支付，支付扣款成功，但是返回结果的时候网络异常，此时钱已经扣了，用户再次点击按钮，此时会进行第二次扣款，返回结果成功，用户查询余额发现多扣钱了，流水记录也变成了两条。在以前的单应用系统中，我们只需要把数据操作放入事务中即可，发生错误立即回滚，但是再响应客户端的时候也有可能出现网络中断或者异常等等。而<strong>幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用</strong></p>
<h3 id="11-1-2-消息重复消费"><a href="#11-1-2-消息重复消费" class="headerlink" title="11.1.2.消息重复消费"></a>11.1.2.消息重复消费</h3><p>消费者在消费 MQ 中的消息时，MQ 已把消息发送给消费者，消费者在给 MQ 返回 ack 时网络中断，故 MQ 未收到确认信息，该条消息会重新发给其他的消费者，或者在网络重连后再次发送给该消费者，但实际上该消费者已成功消费了该条消息，造成消费者消费了重复的消息。</p>
<h3 id="11-1-3-解决思路"><a href="#11-1-3-解决思路" class="headerlink" title="11.1.3.解决思路"></a>11.1.3.解决思路</h3><p>MQ 消费者的幂等性的解决一般使用全局 ID 或者写个唯一标识比如时间戳 或者 UUID 或者订单消费者消费 MQ 中的消息也可利用 MQ 的该 id 来判断，或者可按自己的规则生成一个全局唯一 id，每次消费消息时<strong>用该 id 先判断该消息是否已消费过。</strong></p>
<h3 id="11-1-4-消费端的幂等性保障"><a href="#11-1-4-消费端的幂等性保障" class="headerlink" title="11.1.4.消费端的幂等性保障"></a>11.1.4.消费端的幂等性保障</h3><p>在海量订单生成的业务高峰期，生产端有可能就会重复发生了消息，这时候消费端就要实现幂等性，这就意味着我们的消息永远不会被消费多次，即使我们收到了一样的消息。业界主流的幂等性有两种操作:</p>
<ul>
<li><p>a.唯一 ID+指纹码机制,利用数据库主键去重,</p>
</li>
<li><p>b.利用 redis 的原子性去实现</p>
</li>
</ul>
<h3 id="11-1-5-唯一ID-指纹码机制"><a href="#11-1-5-唯一ID-指纹码机制" class="headerlink" title="11.1.5.唯一ID+指纹码机制"></a>11.1.5.唯一ID+指纹码机制</h3><p>指纹码:我们的一些规则或者时间戳加别的服务给到的唯一信息码,它并不一定是我们系统生成的，基本都是由我们的业务规则拼接而来，但是一定要保证唯一性，然后就利用查询语句进行判断这个 id 是否存在数据库中,优势就是实现简单就一个拼接，然后查询判断是否重复；劣势就是在高并发时，如果是单个数据库就会有写入性能瓶颈当然也可以采用分库分表提升性能，但也不是我们最推荐的方式。</p>
<h3 id="11-1-6-Redis原子性"><a href="#11-1-6-Redis原子性" class="headerlink" title="11.1.6.Redis原子性"></a>11.1.6.Redis原子性</h3><p>利用 redis 执行 setnx 命令，天然具有幂等性。从而实现不重复消费</p>
<h2 id="11-2-优先级队列"><a href="#11-2-优先级队列" class="headerlink" title="11.2.优先级队列"></a>11.2.优先级队列</h2><h3 id="11-2-1-使用场景"><a href="#11-2-1-使用场景" class="headerlink" title="11.2.1.使用场景"></a>11.2.1.使用场景</h3><p>在我们系统中有一个<strong>订单催付</strong>的场景，我们的客户在天猫下的订单,淘宝会及时将订单推送给我们，如果在用户设定的时间内未付款那么就会给用户推送一条短信提醒，很简单的一个功能对吧，但是，天猫商家对我们来说，肯定是要分大客户和小客户的对吧，比如像苹果，小米这样大商家一年起码能给我们创造很大的利润，所以理应当然，他们的订单必须得到优先处理，所以<strong>订单量大了后采用 RabbitMQ 进行改造和优化,如果发现是大客户的订单给一个相对比较高的优先级，否则就是默认优先级。</strong></p>
<h3 id="11-2-2-如何添加"><a href="#11-2-2-如何添加" class="headerlink" title="11.2.2.如何添加"></a>11.2.2.如何添加</h3><p>要让队列实现优先级需要做的事情有如下事情:<strong>队列需要设置为优先级队列，消息需要设置消息的优先级</strong>，消费者需要等待消息已经发送到队列中才去消费，因为这样才有机会对消息进行排序</p>
<p>前面学过的为队列设置优先级</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Map&lt;String, Object&gt; params = <span class="keyword">new</span> HashMap();</span><br><span class="line">params.put(<span class="string">&quot;x-max-priority&quot;</span>, <span class="number">10</span>);</span><br><span class="line">channel.queueDeclare(<span class="string">&quot;hello&quot;</span>, <span class="keyword">true</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, params);</span><br></pre></td></tr></table></figure>

<p>为消息设置优先级：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">AMQP.BasicProperties properties = <span class="keyword">new</span> AMQP.BasicProperties().builder().priority(<span class="number">5</span>).build();</span><br><span class="line">channel.basicPublish(<span class="string">&quot;&quot;</span>,QUEUE_NAME,properties,message.getBytes());</span><br></pre></td></tr></table></figure>

<h3 id="11-2-3-实战"><a href="#11-2-3-实战" class="headerlink" title="11.2.3.实战"></a>11.2.3.实战</h3><h4 id="1）消息生产者"><a href="#1）消息生产者" class="headerlink" title="1）消息生产者"></a>1）消息生产者</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Producer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">//创建连接工厂</span></span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        <span class="comment">//工厂ip连接RabbitMQ队列</span></span><br><span class="line">        factory.setHost(HOST);</span><br><span class="line">        <span class="comment">//用户名</span></span><br><span class="line">        factory.setUsername(<span class="string">&quot;admin&quot;</span>);</span><br><span class="line">        <span class="comment">//密码</span></span><br><span class="line">        factory.setPassword(<span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//创建连接</span></span><br><span class="line">        Connection connection = factory.newConnection();</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 生成队列</span></span><br><span class="line"><span class="comment">         * 1.队列名称</span></span><br><span class="line"><span class="comment">         * 2.队列中的消息是否持久化（磁盘），默认情况消息存储在内存中</span></span><br><span class="line"><span class="comment">         * 3.是否只供一个消费者进行消费，是否进行消息的共享，true可以多个消费者进行消费，默认情况下是false</span></span><br><span class="line"><span class="comment">         * 4.是否自动删除，最后一个消费者开连接以后，该队列是否自动删除，true自动删除，反之亦然。</span></span><br><span class="line"><span class="comment">         * 5.其它参数</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        Map&lt;String,Object&gt; arguments = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        arguments.put(<span class="string">&quot;x-max-priority&quot;</span>,<span class="number">10</span>);</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,arguments);</span><br><span class="line">        <span class="comment">//发消息</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">10</span>; i++)&#123;</span><br><span class="line">            String message = <span class="string">&quot;info&quot;</span> + i;</span><br><span class="line">            <span class="keyword">if</span>(i == <span class="number">5</span>)&#123;</span><br><span class="line">                <span class="comment">//第5条消息优先级为5</span></span><br><span class="line">                AMQP.BasicProperties properties = <span class="keyword">new</span> AMQP.BasicProperties.Builder().priority(<span class="number">5</span>).build();</span><br><span class="line">                <span class="comment">/**</span></span><br><span class="line"><span class="comment">                 * 参数含义：</span></span><br><span class="line"><span class="comment">                 * 1.发送到哪个交换机</span></span><br><span class="line"><span class="comment">                 * 2.路由的key值是哪个，本次是队列的名称</span></span><br><span class="line"><span class="comment">                 * 3.其他参数信息</span></span><br><span class="line"><span class="comment">                 * 4.发送消息的消息体</span></span><br><span class="line"><span class="comment">                 */</span></span><br><span class="line">                channel.basicPublish(<span class="string">&quot;&quot;</span>,QUEUE_NAME,properties,message.getBytes());</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                channel.basicPublish(<span class="string">&quot;&quot;</span>,QUEUE_NAME,<span class="keyword">null</span>,message.getBytes());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;====消息发送完毕====&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="2）消息消费者"><a href="#2）消息消费者" class="headerlink" title="2）消息消费者"></a>2）消息消费者</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Consumer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String HOST = <span class="string">&quot;192.168.227.50&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, TimeoutException </span>&#123;</span><br><span class="line">        <span class="comment">//创建连接工厂</span></span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        factory.setHost(HOST);</span><br><span class="line">        factory.setUsername(<span class="string">&quot;admin&quot;</span>);</span><br><span class="line">        factory.setPassword(<span class="string">&quot;123&quot;</span>);</span><br><span class="line">        <span class="comment">//创建连接</span></span><br><span class="line">        Connection connection = factory.newConnection();</span><br><span class="line">        <span class="comment">//创建信道</span></span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        <span class="comment">//接收消息</span></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 参数说明：</span></span><br><span class="line"><span class="comment">         * 1.消费哪个队列</span></span><br><span class="line"><span class="comment">         * 2.消费成功后是否自动签收</span></span><br><span class="line"><span class="comment">         * 3.消费者消费的回调</span></span><br><span class="line"><span class="comment">         * 4.消费者取消消费的回调</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>,</span><br><span class="line">                (s,delivery) -&gt; System.out.println(<span class="keyword">new</span> String(delivery.getBody())), <span class="comment">//打印消息体</span></span><br><span class="line">                (s) -&gt; System.out.println(<span class="string">&quot;消息被中断&#123;&#125;&quot;</span>+s)</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="11-3-惰性队列"><a href="#11-3-惰性队列" class="headerlink" title="11.3.惰性队列"></a>11.3.惰性队列</h2><h3 id="11-3-1-使用场景"><a href="#11-3-1-使用场景" class="headerlink" title="11.3.1.使用场景"></a>11.3.1.使用场景</h3><p>RabbitMQ 从 3.6.0 版本开始引入了惰性队列的概念。<strong>惰性队列</strong>会尽可能的<strong>将消息存入磁盘</strong>中，而在<strong>消费者消费到相应的消息时才会被加载到内存中</strong>，它的一个重要的设计目标是能够支持更长的队列，即支持更多的消息存储。当消费者由于各种各样的原因(比如消费者下线、宕机亦或者是由于维护而关闭等)而致使长时间内不能消费消息造成堆积时，惰性队列就很有必要了。</p>
<p>默认情况下，当生产者将消息发送到 RabbitMQ 的时候，<strong>队列中的消息会尽可能的存储在内存之中，这样可以更加快速的将消息发送给消费者</strong>。即使是<strong>持久化的消息</strong>，<strong>在被写入磁盘的同时也会在内存中驻留一份备份</strong>。当 RabbitMQ <strong>需要释放内存的时候，会将内存中的消息换页至磁盘中</strong>，这个操作会<strong>耗费较长的时间</strong>，也会阻塞队列的操作，进而无法接收新的消息。虽然 RabbitMQ 的开发者们一直在升级相关的算法，但是效果始终不太理想，尤其是在消息量特别大的时候</p>
<h3 id="11-3-2-两种模式"><a href="#11-3-2-两种模式" class="headerlink" title="11.3.2.两种模式"></a>11.3.2.两种模式</h3><p>队列具备两种模式：default 和 lazy。默认的为 default 模式，在 3.6.0 之前的版本无需做任何变更。lazy模式即为惰性队列的模式，可以通过调用 channel.queueDeclare 方法的时候在参数中设置，也可以通过Policy 的方式设置，<strong>如果一个队列同时使用这两种方式设置的话，那么 Policy 的方式具备更高的优先级</strong>。如果要<strong>通过声明的方式改变已有队列的模式的话，那么只能先删除队列，然后再重新声明一个新的</strong>。</p>
<p>在队列声明的时候可以通过“<strong>x-queue-mode</strong>”参数来设置队列的模式，取值为“default”和“lazy”。下面示例中演示了一个惰性队列的声明细节：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Map&lt;String, Object&gt; args = <span class="keyword">new</span> HashMap&lt;String, Object&gt;();</span><br><span class="line"></span><br><span class="line">args.put(<span class="string">&quot;x-queue-mode&quot;</span>, <span class="string">&quot;lazy&quot;</span>);</span><br><span class="line"></span><br><span class="line">channel.queueDeclare(<span class="string">&quot;myqueue&quot;</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, args);</span><br></pre></td></tr></table></figure>

<h3 id="11-3-3-内存开销对比"><a href="#11-3-3-内存开销对比" class="headerlink" title="11.3.3.内存开销对比"></a>11.3.3.内存开销对比</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202111743138.png" alt="image-20220211172433735"></p>
<p>在发送 1 百万条消息，每条消息大概占 1KB 的情况下，普通队列占用内存是 1.2GB，而惰性队列仅仅占用 1.5MB。而这1.5MB是存放磁盘中个消息的索引，当需要消费时，根据索引进行消费。</p>
<h1 id="十二、RabbitMQ集群"><a href="#十二、RabbitMQ集群" class="headerlink" title="十二、RabbitMQ集群"></a>十二、RabbitMQ集群</h1><h2 id="12-1-clustering"><a href="#12-1-clustering" class="headerlink" title="12.1.clustering"></a>12.1.clustering</h2><h3 id="12-1-1-使用集群的原因"><a href="#12-1-1-使用集群的原因" class="headerlink" title="12.1.1.使用集群的原因"></a>12.1.1.使用集群的原因</h3><blockquote>
<p>如果 RabbitMQ 服务器遇到内存崩溃、机器掉电或者主板故障等情况，该怎么办？</p>
</blockquote>
<p>单台 RabbitMQ服务器可以满足每秒 1000 条消息的吞吐量，那么如果应用需要 RabbitMQ 服务满足每秒 10 万条消息的吞吐量呢？购买昂贵的服务器来增强单机 RabbitMQ 务的性能显得捉襟见肘，搭建一个 RabbitMQ 集群才是解决实际问题的关键</p>
<h3 id="12-1-2-搭建步骤"><a href="#12-1-2-搭建步骤" class="headerlink" title="12.1.2.搭建步骤"></a>12.1.2.搭建步骤</h3><h4 id="1-修改3台机器的主机名称（注意：修改完后需重启虚拟机）"><a href="#1-修改3台机器的主机名称（注意：修改完后需重启虚拟机）" class="headerlink" title="1.修改3台机器的主机名称（注意：修改完后需重启虚拟机）"></a>1.修改3台机器的主机名称（注意：修改完后需重启虚拟机）</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/hostname</span><br></pre></td></tr></table></figure>

<h4 id="2-配置各个节点的-hosts-文件，让各个节点都能互相识别对方"><a href="#2-配置各个节点的-hosts-文件，让各个节点都能互相识别对方" class="headerlink" title="2.配置各个节点的 hosts 文件，让各个节点都能互相识别对方"></a>2.配置各个节点的 hosts 文件，让各个节点都能互相识别对方</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/hosts</span><br><span class="line"></span><br><span class="line">192.168.227.141 node1</span><br><span class="line">192.168.227.142 node2</span><br><span class="line">192.168.227.143 node3</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202121413417.png" alt="image-20220212141349446"></p>
<h4 id="3-以确保各个节点的-cookie-文件使用的是同一个值"><a href="#3-以确保各个节点的-cookie-文件使用的是同一个值" class="headerlink" title="3.以确保各个节点的 cookie 文件使用的是同一个值"></a>3.以确保各个节点的 cookie 文件使用的是同一个值</h4><p>在node1上执行远程操作命令</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">scp /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/.erlang.cookie</span><br><span class="line">scp /var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq/.erlang.cookie</span><br></pre></td></tr></table></figure>

<p>图例：</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202121349985.png" alt="image-20220212134859679"></p>
<h4 id="4-启动-RabbitMQ-服务-顺带启动-Erlang-虚拟机和-RbbitMQ-应用服务-在三台节点上分别执行以下命令"><a href="#4-启动-RabbitMQ-服务-顺带启动-Erlang-虚拟机和-RbbitMQ-应用服务-在三台节点上分别执行以下命令" class="headerlink" title="4.启动 RabbitMQ 服务,顺带启动 Erlang 虚拟机和 RbbitMQ 应用服务(在三台节点上分别执行以下命令)"></a>4.启动 RabbitMQ 服务,顺带启动 Erlang 虚拟机和 RbbitMQ 应用服务(在三台节点上分别执行以下命令)</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rabbitmq-server -detached</span><br></pre></td></tr></table></figure>

<h4 id="5-在节点-2-执行"><a href="#5-在节点-2-执行" class="headerlink" title="5.在节点 2 执行"></a>5.在节点 2 执行</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl stop_app</span><br><span class="line"></span><br><span class="line">(rabbitmqctl stop 会将 Erlang 虚拟机关闭，rabbitmqctl stop_app 只关闭 RabbitMQ 服务)</span><br><span class="line"></span><br><span class="line">rabbitmqctl reset  <span class="comment">#重置node2</span></span><br><span class="line"></span><br><span class="line">rabbitmqctl join_cluster rabbit@node1  <span class="comment">#加入集群</span></span><br><span class="line"></span><br><span class="line">rabbitmqctl start_app(只启动应用服务)</span><br></pre></td></tr></table></figure>



<h4 id="6-在节点-3-执行"><a href="#6-在节点-3-执行" class="headerlink" title="6.在节点 3 执行"></a>6.在节点 3 执行</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl stop_app</span><br><span class="line"></span><br><span class="line">rabbitmqctl reset</span><br><span class="line"></span><br><span class="line">rabbitmqctl join_cluster rabbit@node2</span><br><span class="line"></span><br><span class="line">rabbitmqctl start_app</span><br></pre></td></tr></table></figure>

<h4 id="7-集群状态"><a href="#7-集群状态" class="headerlink" title="7.集群状态"></a>7.集群状态</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl cluster_status</span><br></pre></td></tr></table></figure>

<h4 id="8-需要重新设置用户"><a href="#8-需要重新设置用户" class="headerlink" title="8.需要重新设置用户"></a>8.需要重新设置用户</h4><p>创建账号</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl add_user admin 123</span><br></pre></td></tr></table></figure>

<p>设置用户角色</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl set_user_tags admin administrator</span><br></pre></td></tr></table></figure>

<p>设置用户权限</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl set_permissions -p <span class="string">&quot;/&quot;</span> admin <span class="string">&quot;.*&quot;</span> <span class="string">&quot;.*&quot;</span> <span class="string">&quot;.*&quot;</span></span><br></pre></td></tr></table></figure>

<h4 id="9-解除集群节点-node2-和-node3-机器分别执行"><a href="#9-解除集群节点-node2-和-node3-机器分别执行" class="headerlink" title="9.解除集群节点(node2 和 node3 机器分别执行)"></a>9.解除集群节点(node2 和 node3 机器分别执行)</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">rabbitmqctl stop_app</span><br><span class="line"></span><br><span class="line">rabbitmqctl reset</span><br><span class="line"></span><br><span class="line">rabbitmqctl start_app</span><br><span class="line"></span><br><span class="line">rabbitmqctl cluster_status</span><br><span class="line"></span><br><span class="line">或者用以下方式</span><br><span class="line">rabbitmqctl -n rabbit@node2 stop_app  (node1机器上执行)  必须将node2的RabbitMQ服务器关闭才能解除集群</span><br><span class="line"></span><br><span class="line">rabbitmqctl forget_cluster_node  rabbit@node2   (node1 机器上执行)</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202121427031.png" alt="image-20220212142728839"></p>
<h4 id="10-在浏览器中随意访问任意一个虚拟机的rabbitmq服务器"><a href="#10-在浏览器中随意访问任意一个虚拟机的rabbitmq服务器" class="headerlink" title="10.在浏览器中随意访问任意一个虚拟机的rabbitmq服务器"></a>10.在浏览器中随意访问任意一个虚拟机的rabbitmq服务器</h4><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202121407864.png" alt="image-20220212140724633"></p>
<h2 id="12-2-镜像队列"><a href="#12-2-镜像队列" class="headerlink" title="12.2.镜像队列"></a>12.2.镜像队列</h2><h3 id="12-2-1-使用镜像的原因"><a href="#12-2-1-使用镜像的原因" class="headerlink" title="12.2.1. 使用镜像的原因"></a>12.2.1. 使用镜像的原因</h3><p>虽然我们做了集群操作，但是目前还不能保证消息不丢失。原因是我们在某台机器上创建的队列是不会自动备份的。所以我们在某台机器发布消息，只会发送到那台机器队列中，当这个机器宕机时，我们就无法消费到消息。引入镜像队列(Mirror Queue)的机制，可以将队列镜像到集群中的其他 Broker 节点之上，如果集群中的一个节点失效了，队列能自动地切换到镜像中的另一个节点上以保证服务的可用性。</p>
<h3 id="12-2-2-搭建步骤"><a href="#12-2-2-搭建步骤" class="headerlink" title="12.2.2. 搭建步骤"></a>12.2.2. 搭建步骤</h3><blockquote>
<p>1.启动三台集群节点</p>
</blockquote>
<blockquote>
<p>2.随便找一个节点添加策略</p>
</blockquote>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202131714117.png" alt="image-20220213170741439"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202131711824.png" alt="image-20220213171136684"></p>
<p>ha-sync-mode有两个值：manual（手动备份）和automatic（自动备份）</p>
<p>pattern的值：^TestMirrior表示以TestMirrior为前缀的队列</p>
<blockquote>
<p>3.在 node1 上创建一个队列发送一条消息，队列存在镜像队列</p>
</blockquote>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132158652.png" alt="image-20220213215839947"></p>
<p>当队列是以mirror开头就会进行备份，也就是所谓的镜像队列</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132158118.png" alt="image-20220213215808905"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132200388.png" alt="image-20220213220015697"></p>
<p>表示当前节点是node1，镜像队列在node3</p>
<blockquote>
<p>4.停掉 node1 之后发现 node2 成为镜像队列</p>
</blockquote>
<p>原因是我们设置了自动备份，当node1宕机，node2就成为备用机。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#让node1宕机,在node1服务器使用以下命令：</span></span><br><span class="line">rabbitmqctl stop_app</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132205753.png" alt="image-20220213220507351"></p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132205130.png" alt="image-20220213220554952"></p>
<blockquote>
<p>5.就算整个集群只剩下一台机器了 依然能消费队列里面的消息</p>
</blockquote>
<p> 说明队列里面的消息被镜像队列传递到相应机器里面了</p>
<h2 id="12-3-Haproxy-Keepalive-实现高可用负载均衡"><a href="#12-3-Haproxy-Keepalive-实现高可用负载均衡" class="headerlink" title="12.3. Haproxy+Keepalive 实现高可用负载均衡"></a>12.3. Haproxy+Keepalive 实现高可用负载均衡</h2><h3 id="12-3-1-整体架构图"><a href="#12-3-1-整体架构图" class="headerlink" title="12.3.1.整体架构图"></a>12.3.1.整体架构图</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132237354.png" alt="image-20220213223738998"></p>
<h3 id="12-3-2-Haproxy-实现负载均衡"><a href="#12-3-2-Haproxy-实现负载均衡" class="headerlink" title="12.3.2. Haproxy 实现负载均衡"></a>12.3.2. Haproxy 实现负载均衡</h3><p>HAProxy 提供高可用性、负载均衡及基于 TCPHTTP 应用的代理，支持虚拟主机，它是免费、快速并且可靠的一种解决方案，包括 Twitter,Reddit,StackOverflow,GitHub 在内的多家知名互联网公司在使用。HAProxy 实现了一种事件驱动、单一进程模型，此模型支持非常大的井发连接数。扩展 nginx,lvs,haproxy 之间的区别: <a target="_blank" rel="noopener" href="http://www.ha97.com/5646.html">http://www.ha97.com/5646.html</a></p>
<h3 id="12-3-3-搭建步骤"><a href="#12-3-3-搭建步骤" class="headerlink" title="12.3.3. 搭建步骤"></a>12.3.3. 搭建步骤</h3><p>1.下载 haproxy(在 node1 和 node2)</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum -y install haproxy</span><br></pre></td></tr></table></figure>

<p>2.修改 node1 和 node2 的 haproxy.cfgvim /etc/haproxy/haproxy.cfg</p>
<p> 需要修改红色 IP 为当前机器 IP</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202150116471.png" alt="image-20220215011643788"></p>
<p>3.在两台节点启动 haproxy</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">haproxy -f /etc/haproxy/haproxy.cfg</span><br><span class="line">ps -ef | grep haproxy</span><br></pre></td></tr></table></figure>

<p>4.访问地址</p>
<p><a target="_blank" rel="noopener" href="http://10.211.55.71:8888/stats">http://10.211.55.71:8888/stats</a> </p>
<h3 id="12-3-4-Keepalived-实现双机-主备-热备"><a href="#12-3-4-Keepalived-实现双机-主备-热备" class="headerlink" title="12.3.4. Keepalived 实现双机(主备)热备"></a>12.3.4. Keepalived 实现双机(主备)热备</h3><p>试想如果前面配置的 HAProxy 主机突然宕机或者网卡失效，那么虽然 RbbitMQ 集群没有任何故障但是对于外界的客户端来说所有的连接都会被断开结果将是灾难性的为了确保负载均衡服务的可靠性同样显得十分重要，这里就要引入 Keepalived 它能够通过自身健康检查、资源接管功能做高可用(双机热备)，实现故障转移.</p>
<h3 id="12-3-5-搭建步骤"><a href="#12-3-5-搭建步骤" class="headerlink" title="12.3.5. 搭建步骤"></a>12.3.5. 搭建步骤</h3><p>1.下载 keepalived</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum -y install keepalived</span><br></pre></td></tr></table></figure>

<p>2.节点 node1 配置文件</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/keepalived/keepalived.conf</span><br></pre></td></tr></table></figure>

<p>把资料里面的 keepalived.conf 修改之后替换</p>
<p>3.节点 node2 配置文件</p>
<p>需要修改 global_defs 的 router_id,如:nodeB</p>
<p>其次要修改 vrrp_instance_VI 中 state 为”BACKUP”；</p>
<p>最后要将 priority 设置为小于 100 的值</p>
<p>4.添加 haproxy_chk.sh</p>
<p>(为了防止 HAProxy 服务挂掉之后 Keepalived 还在正常工作而没有切换到 Backup 上，所以这里需要编写一个脚本来检测 HAProxy 务的状态,当 HAProxy 服务挂掉之后该脚本会自动重启</p>
<p>HAProxy 的服务，如果不成功则关闭 Keepalived 服务，这样便可以切换到 Backup 继续工作)</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/keepalived/haproxy_chk.sh(可以直接上传文件)</span><br><span class="line"></span><br><span class="line">修改权限 chmod 777 /etc/keepalived/haproxy_chk.sh</span><br></pre></td></tr></table></figure>

<p>5.启动 keepalive 命令(node1 和 node2 启动)</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl start keepalived</span><br></pre></td></tr></table></figure>

<p>6.观察 Keepalived 的日志</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tail -f /var/<span class="built_in">log</span>/messages -n 200</span><br></pre></td></tr></table></figure>

<p>7.观察最新添加的 vip</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip add show</span><br></pre></td></tr></table></figure>

<p>8.node1 模拟 keepalived 关闭状态</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl stop keepalived </span><br></pre></td></tr></table></figure>

<p>9.使用 vip 地址来访问 rabbitmq 集群</p>
<h1 id="十三、Federation-Exchange"><a href="#十三、Federation-Exchange" class="headerlink" title="十三、Federation Exchange"></a>十三、Federation Exchange</h1><h2 id="13-1-使用它的原因"><a href="#13-1-使用它的原因" class="headerlink" title="13.1 使用它的原因"></a>13.1 使用它的原因</h2><p>(broker 北京)，(broker 深圳)彼此之间相距甚远，网络延迟是一个不得不面对的问题。有一个在北京的业务(Client 北京) 需要连接(broker 北京)，向其中的交换器 exchangeA 发送消息，此时的网络延迟很小，(Client 北京)可以迅速将消息发送至 exchangeA 中，就算在开启了 publisherconfirm 机制或者事务机制的情况下，也可以迅速收到确认信息。此时又有个在深圳的业务(Client 深圳)需要向 exchangeA 发送消息，那么(Client 深圳) (broker 北京)之间有很大的网络延迟，(Client 深圳) 将发送消息至 exchangeA 会经历一定的延迟，尤其是在开启了 publisherconfirm 机制或者事务机制的情况下，(Client 深圳) 会等待很长的延迟时间来接收(broker 北京)的确认信息，进而必然造成这条发送线程的性能降低，甚至造成一定程度上的阻塞。</p>
<p>将业务(Client 深圳)部署到北京的机房可以解决这个问题，但是如果(Client 深圳)调用的另些服务都部署在深圳，那么又会引发新的时延问题，总不见得将所有业务全部部署在一个机房，那么容灾又何以实现？这里使用 Federation 插件就可以很好地解决这个问题.</p>
<p>利用联合插件将一边的消息同步到另一边。</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132343483.png" alt="image-20220213234350424"></p>
<h2 id="13-2-搭建步骤"><a href="#13-2-搭建步骤" class="headerlink" title="13.2.搭建步骤"></a>13.2.搭建步骤</h2><h3 id="1-需要保证每台节点单独运行"><a href="#1-需要保证每台节点单独运行" class="headerlink" title="1.需要保证每台节点单独运行"></a>1.需要保证每台节点单独运行</h3><h3 id="2-在每台机器上开启federation相关插件"><a href="#2-在每台机器上开启federation相关插件" class="headerlink" title="2.在每台机器上开启federation相关插件"></a>2.在每台机器上开启federation相关插件</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rabbitmq-plugins <span class="built_in">enable</span> rabbitmq_federation</span><br><span class="line">rabbitmq-plugins <span class="built_in">enable</span> rabbitmq_federation_management</span><br></pre></td></tr></table></figure>

<p>之后在浏览器中就会多出两个选项栏</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132352034.png" alt="image-20220213235251356"></p>
<h3 id="3-原理图-先运行-consumer-在-node2-创建-fed-exchange"><a href="#3-原理图-先运行-consumer-在-node2-创建-fed-exchange" class="headerlink" title="3.原理图(先运行 consumer 在 node2 创建 fed_exchange)"></a>3.原理图(先运行 consumer 在 node2 创建 fed_exchange)</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132353942.png" alt="image-20220213235356174"></p>
<p>从图中可以看出node1为上游，node2为下游，node1会将消息同步到node2.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//声明交换机</span></span><br><span class="line">channel.exchangeDeclare(<span class="string">&quot;fed_exchange&quot;</span>,BuiltinExchangeType.DIRECT);</span><br><span class="line"><span class="comment">//声明队列</span></span><br><span class="line">channel.queueDeclare(<span class="string">&quot;node2_queue&quot;</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">false</span>,<span class="keyword">null</span>);</span><br><span class="line"><span class="comment">//绑定队列和交换机</span></span><br><span class="line">channel.queueBind(<span class="string">&quot;node2_queue&quot;</span>,<span class="string">&quot;fed_exchange&quot;</span>,<span class="string">&quot;routeKey&quot;</span>);</span><br></pre></td></tr></table></figure>

<h3 id="4-在downstream（node2）配置upstream（node1）"><a href="#4-在downstream（node2）配置upstream（node1）" class="headerlink" title="4.在downstream（node2）配置upstream（node1）"></a>4.在downstream（node2）配置upstream（node1）</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132252968.png" alt="image-20220213225210266"></p>
<h3 id="5-添加策略"><a href="#5-添加策略" class="headerlink" title="5.添加策略"></a>5.添加策略</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132255511.png" alt="image-20220213225555513"></p>
<p>当前节点为node2，而它的上游是node1，也就是node1将消息同步到node2中</p>
<h3 id="5-成功的前提"><a href="#5-成功的前提" class="headerlink" title="5.成功的前提"></a>5.成功的前提</h3><p>node2处在运行状态</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202132256134.png" alt="image-20220213225654521"></p>
<h1 id="十四、Federation-Queue"><a href="#十四、Federation-Queue" class="headerlink" title="十四、Federation Queue"></a>十四、Federation Queue</h1><h2 id="14-1-使用它的原因"><a href="#14-1-使用它的原因" class="headerlink" title="14.1.使用它的原因"></a>14.1.使用它的原因</h2><p>联邦队列可以在多个 Broker 节点(或者集群)之间为单个队列提供均衡负载的功能。一个联邦队列可以连接一个或者多个上游队列(upstream queue)，并从这些上游队列中获取消息以满足本地消费者消费消息的需求</p>
<h2 id="14-2-搭建步骤"><a href="#14-2-搭建步骤" class="headerlink" title="14.2.搭建步骤"></a>14.2.搭建步骤</h2><h3 id="1-原理图"><a href="#1-原理图" class="headerlink" title="1.原理图"></a>1.原理图</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202150055992.png" alt="image-20220215005518634"></p>
<h3 id="2-添加upstream（同上）"><a href="#2-添加upstream（同上）" class="headerlink" title="2.添加upstream（同上）"></a>2.添加upstream（同上）</h3><h3 id="3-添加策略"><a href="#3-添加策略" class="headerlink" title="3.添加策略"></a>3.添加策略</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202142242348.png" alt="image-20220214224211126"></p>
<h1 id="十五、Shovel"><a href="#十五、Shovel" class="headerlink" title="十五、Shovel"></a>十五、Shovel</h1><h2 id="15-1-使用它的原因"><a href="#15-1-使用它的原因" class="headerlink" title="15.1.使用它的原因"></a>15.1.使用它的原因</h2><p>Federation 具备的数据转发功能类似，Shovel 够可靠、持续地从一个 Broker 中的队列 ( 作为源端，即source ) 拉取数据并转发至另一个 Broker 中的交换器 (作为目的端，即 destination)。作为源端的队列和作为目的端的交换器可以同时位于同一个 Broker，也可以位于不同的 Broker 上。Shovel 可以翻译为”铲子”，是一种比较形象的比喻，这个”铲子”可以将消息从一方”铲子”另一方。Shovel 行为就像优秀的客户端应用程序能够负责连接源和目的地、负责消息的读写及负责连接失败问题的处理</p>
<h2 id="15-2-搭建步骤"><a href="#15-2-搭建步骤" class="headerlink" title="15.2.搭建步骤"></a>15.2.搭建步骤</h2><h3 id="1-开启插件（需要的机器都开启）"><a href="#1-开启插件（需要的机器都开启）" class="headerlink" title="1.开启插件（需要的机器都开启）"></a>1.开启插件（需要的机器都开启）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rabbitmq-plugins <span class="built_in">enable</span> rabbitmq_shovel</span><br><span class="line">rabbitmq-plugins <span class="built_in">enable</span> rabbitmq_shovel_management</span><br></pre></td></tr></table></figure>

<p>开启后在浏览器中会多出两个选项栏</p>
<p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202150059065.png" alt="image-20220215005911616"></p>
<h3 id="2-原理图-在源头发送的消息直接回进入到目的地队列"><a href="#2-原理图-在源头发送的消息直接回进入到目的地队列" class="headerlink" title="2.原理图(在源头发送的消息直接回进入到目的地队列)"></a>2.原理图(在源头发送的消息直接回进入到目的地队列)</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202150110256.png" alt="image-20220215011055678"></p>
<h3 id="3-添加-shovel-源和目的地"><a href="#3-添加-shovel-源和目的地" class="headerlink" title="3.添加 shovel 源和目的地"></a>3.添加 shovel 源和目的地</h3><p><img src="https://gitee.com/RMByuan/note-material/raw/master/studyImg/202202150113542.png" alt="image-20220215011319044"></p>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://example.com/2022/02/21/RabbitMQ%E7%AC%94%E8%AE%B0/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/" rel="tag">消息中间件</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2022/03/05/Spring5%E6%96%B0%E5%8A%9F%E8%83%BD/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            Spring5新功能
          
        </div>
      </a>
    
    
      <a href="/2022/02/14/Maven%E5%AE%89%E8%A3%85%E6%93%8D%E4%BD%9C/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">Maven安装步骤</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "",
    app_key: "",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2022
        <i class="ri-heart-fill heart_icon"></i> Yuan
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="MyBlog"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-3.6.0.min.js"></script>
 
<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->
 
<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: ".tocbot",
    contentSelector: ".article-entry",
    headingSelector: "h1, h2, h3, h4, h5, h6",
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: "main",
    positionFixedSelector: ".tocbot",
    positionFixedClass: "is-position-fixed",
    fixedSidebarOffset: "auto",
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->
 
<script src="/js/clickLove.js"></script>
 
<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/css/clipboard.css">
 <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->

<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    <div id="music">
    
    
    
    <iframe frameborder="no" border="1" marginwidth="0" marginheight="0" width="200" height="52"
        src="//music.163.com/outchain/player?type=2&id=22707008&auto=1&height=32"></iframe>
</div>

<style>
    #music {
        position: fixed;
        right: 15px;
        bottom: 0;
        z-index: 998;
    }
</style>
    
    

  </div>
</body>

</html>